@pellux/goodvibes-agent 0.1.46 → 0.1.48
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 +15 -9
- package/README.md +3 -21
- package/docs/README.md +0 -2
- package/package.json +1 -3
- package/src/cli/completion.ts +0 -1
- package/src/cli/help.ts +0 -11
- package/src/cli/management.ts +0 -6
- package/src/cli/package-verification.ts +0 -1
- package/src/cli/parser.ts +0 -4
- package/src/cli/types.ts +0 -1
- package/src/input/agent-workspace-channels.ts +214 -0
- package/src/input/agent-workspace-setup.ts +121 -0
- package/src/input/agent-workspace.ts +34 -208
- package/src/input/commands/experience-runtime.ts +2 -25
- package/src/input/commands/remote-runtime.ts +5 -5
- package/src/input/commands.ts +0 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +7 -7
- package/src/panels/provider-health-domains.ts +1 -1
- package/src/panels/remote-panel.ts +2 -2
- package/src/renderer/agent-workspace.ts +38 -10
- package/src/tools/agent-context-policy.ts +2 -2
- package/src/verification/live-verifier.ts +0 -15
- package/src/version.ts +1 -1
- package/docs/operator-capability-benchmark.md +0 -104
- package/src/cli/capabilities-command.ts +0 -147
- package/src/config/goodvibes-home-audit.ts +0 -465
- package/src/input/commands/capabilities-runtime.ts +0 -88
- package/src/operator/capability-benchmark.ts +0 -244
- package/src/operator/daemon-capability-audit.ts +0 -1180
|
@@ -1,1180 +0,0 @@
|
|
|
1
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
2
|
-
import { SDK_VERSION } from '../version.ts';
|
|
3
|
-
import type { AgentDaemonConnection } from '../agent/routine-schedule-promotion.ts';
|
|
4
|
-
|
|
5
|
-
export const DAEMON_METHOD_CATALOG_ROUTE = '/api/control-plane/methods';
|
|
6
|
-
export const AGENT_KNOWLEDGE_STATUS_ROUTE = '/api/goodvibes-agent/knowledge/status';
|
|
7
|
-
export const DAEMON_STATUS_ROUTE = '/status';
|
|
8
|
-
|
|
9
|
-
export type DaemonCapabilityAuditFailureKind =
|
|
10
|
-
| 'auth_required'
|
|
11
|
-
| 'daemon_unavailable'
|
|
12
|
-
| 'version_mismatch'
|
|
13
|
-
| 'daemon_route_unavailable'
|
|
14
|
-
| 'daemon_error';
|
|
15
|
-
|
|
16
|
-
export type DaemonCapabilityCoverage = 'ready' | 'partial' | 'missing';
|
|
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';
|
|
25
|
-
|
|
26
|
-
export interface DaemonCapabilityRequirement {
|
|
27
|
-
readonly id: string;
|
|
28
|
-
readonly title: string;
|
|
29
|
-
readonly competitorBaseline: string;
|
|
30
|
-
readonly agentUse: string;
|
|
31
|
-
readonly requiredMethodIds: readonly string[];
|
|
32
|
-
readonly optionalMethodIds: readonly string[];
|
|
33
|
-
readonly requiredAgentRoutes: readonly string[];
|
|
34
|
-
readonly next: readonly string[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface DaemonCapabilityAuditArea {
|
|
38
|
-
readonly id: string;
|
|
39
|
-
readonly title: string;
|
|
40
|
-
readonly coverage: DaemonCapabilityCoverage;
|
|
41
|
-
readonly competitorBaseline: string;
|
|
42
|
-
readonly agentUse: string;
|
|
43
|
-
readonly presentRequiredMethodIds: readonly string[];
|
|
44
|
-
readonly missingRequiredMethodIds: readonly string[];
|
|
45
|
-
readonly presentOptionalMethodIds: readonly string[];
|
|
46
|
-
readonly missingOptionalMethodIds: readonly string[];
|
|
47
|
-
readonly agentRoutes: readonly {
|
|
48
|
-
readonly route: string;
|
|
49
|
-
readonly coverage: DaemonCapabilityRouteCoverage;
|
|
50
|
-
}[];
|
|
51
|
-
readonly routeRisk: {
|
|
52
|
-
readonly readOnlyMethodIds: readonly string[];
|
|
53
|
-
readonly mutatingMethodIds: readonly string[];
|
|
54
|
-
readonly authenticatedMethodIds: readonly string[];
|
|
55
|
-
readonly readOnlyMethodCount: number;
|
|
56
|
-
readonly mutatingMethodCount: number;
|
|
57
|
-
readonly authenticatedMethodCount: number;
|
|
58
|
-
readonly dangerousMethodIds: readonly string[];
|
|
59
|
-
};
|
|
60
|
-
readonly next: readonly string[];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface DaemonCapabilityAuditSuccess {
|
|
64
|
-
readonly ok: true;
|
|
65
|
-
readonly kind: 'daemon.capabilities.audit';
|
|
66
|
-
readonly baseUrl: string;
|
|
67
|
-
readonly daemonVersion: string;
|
|
68
|
-
readonly expectedSdkVersion: string;
|
|
69
|
-
readonly daemonCompatible: boolean;
|
|
70
|
-
readonly methodCatalogRoute: typeof DAEMON_METHOD_CATALOG_ROUTE;
|
|
71
|
-
readonly methodCount: number;
|
|
72
|
-
readonly agentKnowledgeRoute: typeof AGENT_KNOWLEDGE_STATUS_ROUTE;
|
|
73
|
-
readonly agentKnowledgeRouteReady: boolean;
|
|
74
|
-
readonly defaultKnowledgeFallback: false;
|
|
75
|
-
readonly homeGraphFallback: false;
|
|
76
|
-
readonly warnings: readonly string[];
|
|
77
|
-
readonly areas: readonly DaemonCapabilityAuditArea[];
|
|
78
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
export interface DaemonCapabilityAuditFailure {
|
|
186
|
-
readonly ok: false;
|
|
187
|
-
readonly kind: DaemonCapabilityAuditFailureKind;
|
|
188
|
-
readonly error: string;
|
|
189
|
-
readonly baseUrl: string;
|
|
190
|
-
readonly route: string;
|
|
191
|
-
readonly daemonVersion?: string;
|
|
192
|
-
readonly expectedSdkVersion?: string;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export type DaemonCapabilityAuditResult =
|
|
196
|
-
| DaemonCapabilityAuditSuccess
|
|
197
|
-
| DaemonCapabilityAuditFailure;
|
|
198
|
-
|
|
199
|
-
export interface DaemonMethodSummary {
|
|
200
|
-
readonly id: string;
|
|
201
|
-
readonly title?: string;
|
|
202
|
-
readonly category?: string;
|
|
203
|
-
readonly invokable?: boolean;
|
|
204
|
-
readonly access?: string;
|
|
205
|
-
readonly dangerous?: boolean;
|
|
206
|
-
readonly http?: {
|
|
207
|
-
readonly method?: string;
|
|
208
|
-
readonly path?: string;
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
interface FetchJsonResult {
|
|
213
|
-
readonly ok: boolean;
|
|
214
|
-
readonly status: number;
|
|
215
|
-
readonly statusText: string;
|
|
216
|
-
readonly body: unknown;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export const DAEMON_CAPABILITY_REQUIREMENTS: readonly DaemonCapabilityRequirement[] = [
|
|
220
|
-
{
|
|
221
|
-
id: 'gateway-control',
|
|
222
|
-
title: 'Gateway Control Plane',
|
|
223
|
-
competitorBaseline: 'OpenClaw/Hermes expose an always-on gateway with health, auth, method discovery, and events.',
|
|
224
|
-
agentUse: 'Agent connects to the existing GoodVibes daemon, inspects posture, and never starts or owns daemon lifecycle.',
|
|
225
|
-
requiredMethodIds: [
|
|
226
|
-
'control.status',
|
|
227
|
-
'control.auth.current',
|
|
228
|
-
'control.methods.list',
|
|
229
|
-
'control.contract',
|
|
230
|
-
'control.snapshot',
|
|
231
|
-
],
|
|
232
|
-
optionalMethodIds: ['control.clients.list', 'control.events.catalog', 'control.events.stream', 'control.web'],
|
|
233
|
-
requiredAgentRoutes: [],
|
|
234
|
-
next: ['Add richer first-run guidance when auth or route contract checks fail.'],
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
id: 'chat-sessions',
|
|
238
|
-
title: 'Companion Chat And Shared Sessions',
|
|
239
|
-
competitorBaseline: 'Personal agents keep persistent chat and can route larger work through task/session surfaces.',
|
|
240
|
-
agentUse: 'Agent uses companion.chat for normal turns and shared sessions only for explicit TUI build/fix/review delegation.',
|
|
241
|
-
requiredMethodIds: [
|
|
242
|
-
'companion.chat.sessions.create',
|
|
243
|
-
'companion.chat.sessions.get',
|
|
244
|
-
'companion.chat.sessions.list',
|
|
245
|
-
'companion.chat.messages.create',
|
|
246
|
-
'companion.chat.messages.list',
|
|
247
|
-
'sessions.create',
|
|
248
|
-
'sessions.messages.create',
|
|
249
|
-
'sessions.list',
|
|
250
|
-
],
|
|
251
|
-
optionalMethodIds: ['companion.chat.events.stream', 'sessions.followUp', 'sessions.steer', 'sessions.integration.snapshot'],
|
|
252
|
-
requiredAgentRoutes: [],
|
|
253
|
-
next: ['Expose delegated session artifacts and status in the operator workspace without making WRFC default.'],
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
id: 'channels',
|
|
257
|
-
title: 'Channels And Delivery Gateway',
|
|
258
|
-
competitorBaseline: 'Gateway products receive and send across Slack, Discord, webhooks, mobile, and companion surfaces.',
|
|
259
|
-
agentUse: 'Agent inspects channel readiness and uses explicit delivery targets for scheduled routine promotion.',
|
|
260
|
-
requiredMethodIds: [
|
|
261
|
-
'channels.status',
|
|
262
|
-
'channels.capabilities.list',
|
|
263
|
-
'channels.accounts.list',
|
|
264
|
-
'channels.setup.get',
|
|
265
|
-
'channels.doctor.get',
|
|
266
|
-
'channels.actions.list',
|
|
267
|
-
'channels.tools.list',
|
|
268
|
-
'channels.targets.resolve',
|
|
269
|
-
'channels.policies.list',
|
|
270
|
-
],
|
|
271
|
-
optionalMethodIds: [
|
|
272
|
-
'channels.directory.query',
|
|
273
|
-
'channels.allowlist.resolve',
|
|
274
|
-
'channels.policies.audit',
|
|
275
|
-
'channels.agent_tools.list',
|
|
276
|
-
'channels.authorize',
|
|
277
|
-
],
|
|
278
|
-
requiredAgentRoutes: [],
|
|
279
|
-
next: ['Surface live per-account delivery errors and setup repairs in the Agent workspace.'],
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
id: 'agent-knowledge',
|
|
283
|
-
title: 'Isolated Agent Knowledge',
|
|
284
|
-
competitorBaseline: 'Persistent knowledge and memory are core personal-operator features.',
|
|
285
|
-
agentUse: 'Agent Knowledge is a separate product segment under /api/goodvibes-agent/knowledge/* with no default wiki or HomeGraph fallback.',
|
|
286
|
-
requiredMethodIds: [],
|
|
287
|
-
optionalMethodIds: [],
|
|
288
|
-
requiredAgentRoutes: [AGENT_KNOWLEDGE_STATUS_ROUTE],
|
|
289
|
-
next: ['Add artifact and multimodal ingestion UX only against the isolated Agent Knowledge route family.'],
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
id: 'automation-schedules',
|
|
293
|
-
title: 'Automation, Schedules, Runs, And Capacity',
|
|
294
|
-
competitorBaseline: 'Hermes/OpenClaw-style operators can schedule, run, pause, resume, and inspect recurring work.',
|
|
295
|
-
agentUse: 'Agent observes automation and promotes local routines to daemon schedules only through explicit confirmed commands.',
|
|
296
|
-
requiredMethodIds: [
|
|
297
|
-
'automation.integration.snapshot',
|
|
298
|
-
'automation.jobs.list',
|
|
299
|
-
'automation.runs.list',
|
|
300
|
-
'automation.heartbeat.list',
|
|
301
|
-
'schedules.list',
|
|
302
|
-
'schedules.create',
|
|
303
|
-
'scheduler.capacity',
|
|
304
|
-
],
|
|
305
|
-
optionalMethodIds: [
|
|
306
|
-
'automation.jobs.run',
|
|
307
|
-
'automation.jobs.pause',
|
|
308
|
-
'automation.jobs.resume',
|
|
309
|
-
'automation.runs.cancel',
|
|
310
|
-
'automation.runs.retry',
|
|
311
|
-
'schedules.run',
|
|
312
|
-
'schedules.enable',
|
|
313
|
-
'schedules.disable',
|
|
314
|
-
'schedules.delete',
|
|
315
|
-
],
|
|
316
|
-
requiredAgentRoutes: [],
|
|
317
|
-
next: ['Add live delivery/run history and failed delivery diagnostics for promoted Agent routines.'],
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
id: 'approvals-security',
|
|
321
|
-
title: 'Approvals, Policy, And Channel Safety',
|
|
322
|
-
competitorBaseline: 'Exposed agents need approval gates, pairing, allowlists, and policy inspection.',
|
|
323
|
-
agentUse: 'Agent keeps destructive or external effects behind exact commands plus confirmation and uses daemon approvals.',
|
|
324
|
-
requiredMethodIds: [
|
|
325
|
-
'approvals.list',
|
|
326
|
-
'approvals.approve',
|
|
327
|
-
'approvals.deny',
|
|
328
|
-
'approvals.cancel',
|
|
329
|
-
'channels.policies.list',
|
|
330
|
-
'channels.policies.audit',
|
|
331
|
-
],
|
|
332
|
-
optionalMethodIds: ['approvals.claim', 'channels.allowlist.edit', 'channels.allowlist.resolve'],
|
|
333
|
-
requiredAgentRoutes: [],
|
|
334
|
-
next: ['Build a route-risk-aware approval center in the fullscreen Agent workspace.'],
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
id: 'mcp-tools-artifacts',
|
|
338
|
-
title: 'MCP, Tools, Artifacts, And Web Search',
|
|
339
|
-
competitorBaseline: 'Modern personal operators expose managed tools, MCP servers, artifacts, and web/research tools.',
|
|
340
|
-
agentUse: 'Agent uses GoodVibes daemon tool surfaces through public SDK contracts and policy-gated model visibility.',
|
|
341
|
-
requiredMethodIds: [
|
|
342
|
-
'mcp.config.get',
|
|
343
|
-
'mcp.servers.list',
|
|
344
|
-
'mcp.tools.list',
|
|
345
|
-
'artifacts.create',
|
|
346
|
-
'artifacts.get',
|
|
347
|
-
'artifacts.list',
|
|
348
|
-
'web_search.providers.list',
|
|
349
|
-
'web_search.query',
|
|
350
|
-
],
|
|
351
|
-
optionalMethodIds: ['artifacts.content.get', 'mcp.config.reload'],
|
|
352
|
-
requiredAgentRoutes: [],
|
|
353
|
-
next: ['Add per-turn tool-palette narrowing so broad tool capability does not create noisy model schemas.'],
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
id: 'voice-media-nodes',
|
|
357
|
-
title: 'Voice, Media, Multimodal, And Remote Nodes',
|
|
358
|
-
competitorBaseline: 'OpenClaw/Hermes expose voice, media, mobile/node, and multimodal surfaces.',
|
|
359
|
-
agentUse: 'Agent inspects daemon voice/media/remote readiness and keeps execution explicit or read-only until user-selected.',
|
|
360
|
-
requiredMethodIds: [
|
|
361
|
-
'voice.status',
|
|
362
|
-
'voice.providers.list',
|
|
363
|
-
'voice.voices.list',
|
|
364
|
-
'voice.tts',
|
|
365
|
-
'voice.stt',
|
|
366
|
-
'media.providers.list',
|
|
367
|
-
'media.analyze',
|
|
368
|
-
'multimodal.providers.list',
|
|
369
|
-
'remote.snapshot',
|
|
370
|
-
'remote.peers.list',
|
|
371
|
-
'remote.work.list',
|
|
372
|
-
'remote.node_host.contract',
|
|
373
|
-
],
|
|
374
|
-
optionalMethodIds: [
|
|
375
|
-
'voice.realtime.session',
|
|
376
|
-
'voice.tts.stream',
|
|
377
|
-
'media.generate',
|
|
378
|
-
'media.transform',
|
|
379
|
-
'multimodal.analyze',
|
|
380
|
-
'remote.peers.invoke',
|
|
381
|
-
],
|
|
382
|
-
requiredAgentRoutes: [],
|
|
383
|
-
next: ['Turn daemon readiness into Agent setup cards for voice, media, browser, and node workflows.'],
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
id: 'providers-models',
|
|
387
|
-
title: 'Providers, Models, And Usage',
|
|
388
|
-
competitorBaseline: 'Personal operators need configurable providers, model routing, and usage posture.',
|
|
389
|
-
agentUse: 'Agent reads daemon provider/model state, keeps provider+model routing explicit, and avoids per-message routing hacks.',
|
|
390
|
-
requiredMethodIds: ['providers.list', 'providers.get', 'providers.usage.get'],
|
|
391
|
-
optionalMethodIds: ['accounts.snapshot'],
|
|
392
|
-
requiredAgentRoutes: [],
|
|
393
|
-
next: ['Add provider/model readiness remediation directly into onboarding/config workspaces.'],
|
|
394
|
-
},
|
|
395
|
-
] as const;
|
|
396
|
-
|
|
397
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
398
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function readString(record: Record<string, unknown>, key: string): string | null {
|
|
402
|
-
const value = record[key];
|
|
403
|
-
return typeof value === 'string' ? value : null;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function readMethodSummaries(body: unknown): readonly DaemonMethodSummary[] {
|
|
407
|
-
const methods = isRecord(body) && Array.isArray(body.methods) ? body.methods : [];
|
|
408
|
-
return methods.flatMap((value): DaemonMethodSummary[] => {
|
|
409
|
-
if (!isRecord(value)) return [];
|
|
410
|
-
const id = readString(value, 'id');
|
|
411
|
-
if (!id) return [];
|
|
412
|
-
const httpRecord = isRecord(value.http) ? value.http : null;
|
|
413
|
-
return [{
|
|
414
|
-
id,
|
|
415
|
-
title: readString(value, 'title') ?? undefined,
|
|
416
|
-
category: readString(value, 'category') ?? undefined,
|
|
417
|
-
access: readString(value, 'access') ?? undefined,
|
|
418
|
-
invokable: typeof value.invokable === 'boolean' ? value.invokable : undefined,
|
|
419
|
-
dangerous: typeof value.dangerous === 'boolean' ? value.dangerous : undefined,
|
|
420
|
-
http: httpRecord
|
|
421
|
-
? {
|
|
422
|
-
method: readString(httpRecord, 'method') ?? undefined,
|
|
423
|
-
path: readString(httpRecord, 'path') ?? undefined,
|
|
424
|
-
}
|
|
425
|
-
: undefined,
|
|
426
|
-
}];
|
|
427
|
-
});
|
|
428
|
-
}
|
|
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
|
-
|
|
460
|
-
function daemonVersionFromStatus(body: unknown): string {
|
|
461
|
-
if (!isRecord(body)) return 'unknown';
|
|
462
|
-
return readString(body, 'version')
|
|
463
|
-
?? readString(body, 'sdkVersion')
|
|
464
|
-
?? 'unknown';
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
function buildHeaders(connection: AgentDaemonConnection): Headers {
|
|
468
|
-
const headers = new Headers({ accept: 'application/json' });
|
|
469
|
-
if (connection.token) headers.set('authorization', `Bearer ${connection.token}`);
|
|
470
|
-
return headers;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
async function fetchJson(connection: AgentDaemonConnection, route: string): Promise<FetchJsonResult> {
|
|
474
|
-
const response = await fetch(`${connection.baseUrl}${route}`, {
|
|
475
|
-
method: 'GET',
|
|
476
|
-
headers: buildHeaders(connection),
|
|
477
|
-
});
|
|
478
|
-
const text = await response.text();
|
|
479
|
-
let body: unknown = text;
|
|
480
|
-
if (text.trim().length > 0) {
|
|
481
|
-
try {
|
|
482
|
-
body = JSON.parse(text) as unknown;
|
|
483
|
-
} catch {
|
|
484
|
-
body = text;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
return {
|
|
488
|
-
ok: response.ok,
|
|
489
|
-
status: response.status,
|
|
490
|
-
statusText: response.statusText,
|
|
491
|
-
body,
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function failureFromResponse(
|
|
496
|
-
response: FetchJsonResult,
|
|
497
|
-
connection: AgentDaemonConnection,
|
|
498
|
-
route: string,
|
|
499
|
-
daemonVersion: string,
|
|
500
|
-
): DaemonCapabilityAuditFailure {
|
|
501
|
-
const detail = isRecord(response.body) && typeof response.body.error === 'string'
|
|
502
|
-
? response.body.error
|
|
503
|
-
: typeof response.body === 'string'
|
|
504
|
-
? response.body
|
|
505
|
-
: response.statusText;
|
|
506
|
-
const error = `HTTP ${response.status}${detail ? `: ${detail}` : ''}`;
|
|
507
|
-
if (response.status === 401 || response.status === 403) {
|
|
508
|
-
return { ok: false, kind: 'auth_required', error, baseUrl: connection.baseUrl, route };
|
|
509
|
-
}
|
|
510
|
-
if (response.status === 404 && daemonVersion !== 'unknown' && daemonVersion !== SDK_VERSION) {
|
|
511
|
-
return {
|
|
512
|
-
ok: false,
|
|
513
|
-
kind: 'version_mismatch',
|
|
514
|
-
error: `External daemon SDK version ${daemonVersion} does not match Agent SDK pin ${SDK_VERSION}; ${route} is unavailable.`,
|
|
515
|
-
baseUrl: connection.baseUrl,
|
|
516
|
-
route,
|
|
517
|
-
daemonVersion,
|
|
518
|
-
expectedSdkVersion: SDK_VERSION,
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
if (response.status === 404) {
|
|
522
|
-
return { ok: false, kind: 'daemon_route_unavailable', error, baseUrl: connection.baseUrl, route };
|
|
523
|
-
}
|
|
524
|
-
return { ok: false, kind: 'daemon_error', error, baseUrl: connection.baseUrl, route };
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function failureFromThrown(error: unknown, connection: AgentDaemonConnection, route: string): DaemonCapabilityAuditFailure {
|
|
528
|
-
const message = summarizeError(error);
|
|
529
|
-
const lower = message.toLowerCase();
|
|
530
|
-
if (lower.includes('unauthorized') || lower.includes('401') || lower.includes('403')) {
|
|
531
|
-
return { ok: false, kind: 'auth_required', error: message, baseUrl: connection.baseUrl, route };
|
|
532
|
-
}
|
|
533
|
-
if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
|
|
534
|
-
return { ok: false, kind: 'daemon_unavailable', error: message, baseUrl: connection.baseUrl, route };
|
|
535
|
-
}
|
|
536
|
-
if (lower.includes('404') || lower.includes('not found')) {
|
|
537
|
-
return { ok: false, kind: 'daemon_route_unavailable', error: message, baseUrl: connection.baseUrl, route };
|
|
538
|
-
}
|
|
539
|
-
return { ok: false, kind: 'daemon_error', error: message, baseUrl: connection.baseUrl, route };
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
export function buildDaemonCapabilityAuditAreas(
|
|
543
|
-
methodIds: ReadonlySet<string>,
|
|
544
|
-
agentKnowledgeRouteReady: boolean | null,
|
|
545
|
-
methodSummaries: readonly DaemonMethodSummary[] = [],
|
|
546
|
-
): readonly DaemonCapabilityAuditArea[] {
|
|
547
|
-
const methodsById = new Map(methodSummaries.map((method) => [method.id, method]));
|
|
548
|
-
return DAEMON_CAPABILITY_REQUIREMENTS.map((requirement) => {
|
|
549
|
-
const presentRequiredMethodIds = requirement.requiredMethodIds.filter((methodId) => methodIds.has(methodId));
|
|
550
|
-
const missingRequiredMethodIds = requirement.requiredMethodIds.filter((methodId) => !methodIds.has(methodId));
|
|
551
|
-
const presentOptionalMethodIds = requirement.optionalMethodIds.filter((methodId) => methodIds.has(methodId));
|
|
552
|
-
const missingOptionalMethodIds = requirement.optionalMethodIds.filter((methodId) => !methodIds.has(methodId));
|
|
553
|
-
const agentRoutes = requirement.requiredAgentRoutes.map((route) => ({
|
|
554
|
-
route,
|
|
555
|
-
coverage: agentKnowledgeRouteReady === null
|
|
556
|
-
? 'not_checked'
|
|
557
|
-
: agentKnowledgeRouteReady
|
|
558
|
-
? 'ready'
|
|
559
|
-
: 'missing',
|
|
560
|
-
} satisfies DaemonCapabilityAuditArea['agentRoutes'][number]));
|
|
561
|
-
const missingAgentRoutes = agentRoutes.filter((route) => route.coverage === 'missing');
|
|
562
|
-
const areaMethodIds = [...requirement.requiredMethodIds, ...requirement.optionalMethodIds];
|
|
563
|
-
const areaMethods = areaMethodIds.flatMap((methodId): DaemonMethodSummary[] => {
|
|
564
|
-
const method = methodsById.get(methodId);
|
|
565
|
-
return method ? [method] : [];
|
|
566
|
-
});
|
|
567
|
-
const readOnlyMethodIds = areaMethods.filter((method) => {
|
|
568
|
-
const verb = method.http?.method?.toUpperCase();
|
|
569
|
-
return verb === 'GET' || verb === 'HEAD';
|
|
570
|
-
}).map((method) => method.id);
|
|
571
|
-
const mutatingMethodIds = areaMethods.filter((method) => {
|
|
572
|
-
const verb = method.http?.method?.toUpperCase();
|
|
573
|
-
return Boolean(verb) && verb !== 'GET' && verb !== 'HEAD';
|
|
574
|
-
}).map((method) => method.id);
|
|
575
|
-
const authenticatedMethodIds = areaMethods
|
|
576
|
-
.filter((method) => method.access === 'authenticated')
|
|
577
|
-
.map((method) => method.id);
|
|
578
|
-
const dangerousMethodIds = areaMethods
|
|
579
|
-
.filter((method) => method.dangerous === true)
|
|
580
|
-
.map((method) => method.id);
|
|
581
|
-
const requiredCount = requirement.requiredMethodIds.length + requirement.requiredAgentRoutes.length;
|
|
582
|
-
const presentRequiredCount = presentRequiredMethodIds.length
|
|
583
|
-
+ agentRoutes.filter((route) => route.coverage === 'ready').length;
|
|
584
|
-
const coverage: DaemonCapabilityCoverage = requiredCount === presentRequiredCount
|
|
585
|
-
? 'ready'
|
|
586
|
-
: presentRequiredCount > 0 && missingRequiredMethodIds.length + missingAgentRoutes.length < requiredCount
|
|
587
|
-
? 'partial'
|
|
588
|
-
: 'missing';
|
|
589
|
-
return {
|
|
590
|
-
id: requirement.id,
|
|
591
|
-
title: requirement.title,
|
|
592
|
-
coverage,
|
|
593
|
-
competitorBaseline: requirement.competitorBaseline,
|
|
594
|
-
agentUse: requirement.agentUse,
|
|
595
|
-
presentRequiredMethodIds,
|
|
596
|
-
missingRequiredMethodIds,
|
|
597
|
-
presentOptionalMethodIds,
|
|
598
|
-
missingOptionalMethodIds,
|
|
599
|
-
agentRoutes,
|
|
600
|
-
routeRisk: {
|
|
601
|
-
readOnlyMethodIds,
|
|
602
|
-
mutatingMethodIds,
|
|
603
|
-
authenticatedMethodIds,
|
|
604
|
-
readOnlyMethodCount: readOnlyMethodIds.length,
|
|
605
|
-
mutatingMethodCount: mutatingMethodIds.length,
|
|
606
|
-
authenticatedMethodCount: authenticatedMethodIds.length,
|
|
607
|
-
dangerousMethodIds,
|
|
608
|
-
},
|
|
609
|
-
next: requirement.next,
|
|
610
|
-
};
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
export async function fetchLiveDaemonCapabilityAudit(
|
|
615
|
-
connection: AgentDaemonConnection,
|
|
616
|
-
): Promise<DaemonCapabilityAuditResult> {
|
|
617
|
-
let daemonVersion = 'unknown';
|
|
618
|
-
try {
|
|
619
|
-
const status = await fetchJson(connection, DAEMON_STATUS_ROUTE);
|
|
620
|
-
if (status.ok) {
|
|
621
|
-
daemonVersion = daemonVersionFromStatus(status.body);
|
|
622
|
-
} else if (status.status === 401 || status.status === 403) {
|
|
623
|
-
return failureFromResponse(status, connection, DAEMON_STATUS_ROUTE, daemonVersion);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const methods = await fetchJson(connection, DAEMON_METHOD_CATALOG_ROUTE);
|
|
627
|
-
if (!methods.ok) return failureFromResponse(methods, connection, DAEMON_METHOD_CATALOG_ROUTE, daemonVersion);
|
|
628
|
-
|
|
629
|
-
const methodSummaries = readMethodSummaries(methods.body);
|
|
630
|
-
const methodIds = new Set(methodSummaries.map((method) => method.id));
|
|
631
|
-
const warnings: string[] = [];
|
|
632
|
-
const daemonCompatible = daemonVersion === SDK_VERSION;
|
|
633
|
-
if (daemonVersion !== 'unknown' && !daemonCompatible) {
|
|
634
|
-
warnings.push(`External daemon SDK version ${daemonVersion} does not match Agent SDK pin ${SDK_VERSION}.`);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const agentKnowledge = await fetchJson(connection, AGENT_KNOWLEDGE_STATUS_ROUTE);
|
|
638
|
-
if (!agentKnowledge.ok) {
|
|
639
|
-
const failure = failureFromResponse(agentKnowledge, connection, AGENT_KNOWLEDGE_STATUS_ROUTE, daemonVersion);
|
|
640
|
-
if (failure.kind === 'auth_required') return failure;
|
|
641
|
-
warnings.push(`${AGENT_KNOWLEDGE_STATUS_ROUTE} is not ready: ${failure.error}`);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
return {
|
|
645
|
-
ok: true,
|
|
646
|
-
kind: 'daemon.capabilities.audit',
|
|
647
|
-
baseUrl: connection.baseUrl,
|
|
648
|
-
daemonVersion,
|
|
649
|
-
expectedSdkVersion: SDK_VERSION,
|
|
650
|
-
daemonCompatible,
|
|
651
|
-
methodCatalogRoute: DAEMON_METHOD_CATALOG_ROUTE,
|
|
652
|
-
methodCount: methodSummaries.length,
|
|
653
|
-
agentKnowledgeRoute: AGENT_KNOWLEDGE_STATUS_ROUTE,
|
|
654
|
-
agentKnowledgeRouteReady: agentKnowledge.ok,
|
|
655
|
-
defaultKnowledgeFallback: false,
|
|
656
|
-
homeGraphFallback: false,
|
|
657
|
-
warnings,
|
|
658
|
-
areas: buildDaemonCapabilityAuditAreas(methodIds, agentKnowledge.ok, methodSummaries),
|
|
659
|
-
};
|
|
660
|
-
} catch (error) {
|
|
661
|
-
return failureFromThrown(error, connection, daemonVersion === 'unknown' ? DAEMON_STATUS_ROUTE : DAEMON_METHOD_CATALOG_ROUTE);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
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
|
-
|
|
775
|
-
export function filterDaemonCapabilityAuditAreas(
|
|
776
|
-
areas: readonly DaemonCapabilityAuditArea[],
|
|
777
|
-
query: string | undefined,
|
|
778
|
-
): readonly DaemonCapabilityAuditArea[] {
|
|
779
|
-
const normalized = query?.trim().toLowerCase();
|
|
780
|
-
if (!normalized) return areas;
|
|
781
|
-
return areas.filter((area) => {
|
|
782
|
-
if (area.id.includes(normalized)) return true;
|
|
783
|
-
if (area.title.toLowerCase().includes(normalized)) return true;
|
|
784
|
-
if (area.coverage.includes(normalized)) return true;
|
|
785
|
-
if (area.agentUse.toLowerCase().includes(normalized)) return true;
|
|
786
|
-
return area.presentRequiredMethodIds.some((methodId) => methodId.includes(normalized))
|
|
787
|
-
|| area.missingRequiredMethodIds.some((methodId) => methodId.includes(normalized))
|
|
788
|
-
|| area.presentOptionalMethodIds.some((methodId) => methodId.includes(normalized))
|
|
789
|
-
|| area.missingOptionalMethodIds.some((methodId) => methodId.includes(normalized))
|
|
790
|
-
|| area.agentRoutes.some((route) => route.route.toLowerCase().includes(normalized));
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
function gapToken(value: string): string {
|
|
795
|
-
return value
|
|
796
|
-
.toLowerCase()
|
|
797
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
798
|
-
.replace(/^-+|-+$/g, '')
|
|
799
|
-
|| 'gap';
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
function gapSeverityRank(severity: DaemonCapabilityGapSeverity): number {
|
|
803
|
-
if (severity === 'blocker') return 0;
|
|
804
|
-
if (severity === 'high') return 1;
|
|
805
|
-
if (severity === 'medium') return 2;
|
|
806
|
-
return 3;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
function sortCapabilityGaps(gaps: readonly DaemonCapabilityGap[]): readonly DaemonCapabilityGap[] {
|
|
810
|
-
return [...gaps].sort((left, right) => {
|
|
811
|
-
const severityDelta = gapSeverityRank(left.severity) - gapSeverityRank(right.severity);
|
|
812
|
-
if (severityDelta !== 0) return severityDelta;
|
|
813
|
-
return left.id.localeCompare(right.id);
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
export function buildDaemonCapabilityGapReport(
|
|
818
|
-
audit: DaemonCapabilityAuditSuccess,
|
|
819
|
-
areas: readonly DaemonCapabilityAuditArea[] = audit.areas,
|
|
820
|
-
): DaemonCapabilityGapReport {
|
|
821
|
-
const gaps: DaemonCapabilityGap[] = [];
|
|
822
|
-
|
|
823
|
-
if (!audit.daemonCompatible) {
|
|
824
|
-
gaps.push({
|
|
825
|
-
id: 'daemon-version-mismatch',
|
|
826
|
-
kind: 'version_mismatch',
|
|
827
|
-
severity: audit.agentKnowledgeRouteReady ? 'high' : 'blocker',
|
|
828
|
-
title: 'Daemon SDK version does not match Agent SDK pin',
|
|
829
|
-
detail: `Agent expects ${audit.expectedSdkVersion}; daemon reports ${audit.daemonVersion}.`,
|
|
830
|
-
action: 'Update/restart the externally owned GoodVibes daemon before release validation; Agent will not start it.',
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
for (const area of areas) {
|
|
835
|
-
if (area.missingRequiredMethodIds.length > 0) {
|
|
836
|
-
gaps.push({
|
|
837
|
-
id: `${area.id}-missing-required-methods`,
|
|
838
|
-
kind: 'required_method_missing',
|
|
839
|
-
severity: 'high',
|
|
840
|
-
areaId: area.id,
|
|
841
|
-
title: `${area.title} missing required daemon methods`,
|
|
842
|
-
detail: area.missingRequiredMethodIds.join(', '),
|
|
843
|
-
action: 'Keep the Agent surface read-only or blocked for this area until the public daemon route contract is present.',
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
for (const route of area.agentRoutes) {
|
|
848
|
-
if (route.coverage !== 'missing') continue;
|
|
849
|
-
gaps.push({
|
|
850
|
-
id: `${area.id}-missing-${gapToken(route.route)}`,
|
|
851
|
-
kind: 'agent_route_missing',
|
|
852
|
-
severity: route.route === AGENT_KNOWLEDGE_STATUS_ROUTE ? 'blocker' : 'high',
|
|
853
|
-
areaId: area.id,
|
|
854
|
-
title: `${area.title} missing Agent route`,
|
|
855
|
-
detail: route.route,
|
|
856
|
-
action: 'Fail closed for this product segment. Do not query default Knowledge/Wiki, HomeGraph, or Home Assistant routes.',
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
if (area.routeRisk.dangerousMethodIds.length > 0) {
|
|
861
|
-
gaps.push({
|
|
862
|
-
id: `${area.id}-dangerous-route-review`,
|
|
863
|
-
kind: 'route_risk_review',
|
|
864
|
-
severity: 'medium',
|
|
865
|
-
areaId: area.id,
|
|
866
|
-
title: `${area.title} has dangerous daemon routes`,
|
|
867
|
-
detail: area.routeRisk.dangerousMethodIds.join(', '),
|
|
868
|
-
action: 'Keep these routes behind exact commands, confirmation, and concise approval UX; never trigger them from ordinary chat.',
|
|
869
|
-
});
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
for (const next of area.next) {
|
|
873
|
-
gaps.push({
|
|
874
|
-
id: `${area.id}-agent-ux-${gapToken(next)}`,
|
|
875
|
-
kind: 'agent_ux_gap',
|
|
876
|
-
severity: area.coverage === 'ready' ? 'medium' : 'low',
|
|
877
|
-
areaId: area.id,
|
|
878
|
-
title: `${area.title} Agent UX gap`,
|
|
879
|
-
detail: next,
|
|
880
|
-
action: 'Build a first-class Agent workspace, command, or setup flow on top of the existing daemon capability.',
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
const sortedGaps = sortCapabilityGaps(gaps);
|
|
886
|
-
return {
|
|
887
|
-
ok: true,
|
|
888
|
-
kind: 'daemon.capabilities.gaps',
|
|
889
|
-
baseUrl: audit.baseUrl,
|
|
890
|
-
daemonVersion: audit.daemonVersion,
|
|
891
|
-
expectedSdkVersion: audit.expectedSdkVersion,
|
|
892
|
-
daemonCompatible: audit.daemonCompatible,
|
|
893
|
-
methodCatalogRoute: audit.methodCatalogRoute,
|
|
894
|
-
agentKnowledgeRoute: audit.agentKnowledgeRoute,
|
|
895
|
-
agentKnowledgeRouteReady: audit.agentKnowledgeRouteReady,
|
|
896
|
-
defaultKnowledgeFallback: false,
|
|
897
|
-
homeGraphFallback: false,
|
|
898
|
-
gapCount: sortedGaps.length,
|
|
899
|
-
gaps: sortedGaps,
|
|
900
|
-
};
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
export function filterDaemonCapabilityGaps(
|
|
904
|
-
gaps: readonly DaemonCapabilityGap[],
|
|
905
|
-
query: string | undefined,
|
|
906
|
-
): readonly DaemonCapabilityGap[] {
|
|
907
|
-
const normalized = query?.trim().toLowerCase();
|
|
908
|
-
if (!normalized) return gaps;
|
|
909
|
-
return gaps.filter((gap) => {
|
|
910
|
-
return gap.id.includes(normalized)
|
|
911
|
-
|| gap.kind.includes(normalized)
|
|
912
|
-
|| gap.severity.includes(normalized)
|
|
913
|
-
|| gap.title.toLowerCase().includes(normalized)
|
|
914
|
-
|| gap.detail.toLowerCase().includes(normalized)
|
|
915
|
-
|| gap.action.toLowerCase().includes(normalized)
|
|
916
|
-
|| Boolean(gap.areaId?.includes(normalized));
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
export function renderDaemonCapabilityGaps(
|
|
921
|
-
report: DaemonCapabilityGapReport,
|
|
922
|
-
gaps: readonly DaemonCapabilityGap[] = report.gaps,
|
|
923
|
-
): string {
|
|
924
|
-
const lines: string[] = [
|
|
925
|
-
'GoodVibes daemon capability gaps',
|
|
926
|
-
` daemon: ${report.baseUrl}`,
|
|
927
|
-
` SDK: Agent expects ${report.expectedSdkVersion}; daemon reports ${report.daemonVersion}`,
|
|
928
|
-
` compatibility: ${report.daemonCompatible ? 'matched' : 'mismatch'}`,
|
|
929
|
-
` Agent Knowledge: ${report.agentKnowledgeRouteReady ? 'ready' : 'missing'} ${report.agentKnowledgeRoute}`,
|
|
930
|
-
' isolation: default Knowledge/Wiki fallback no; HomeGraph fallback no',
|
|
931
|
-
` gaps: ${gaps.length}/${report.gapCount}`,
|
|
932
|
-
'',
|
|
933
|
-
];
|
|
934
|
-
|
|
935
|
-
if (gaps.length === 0) {
|
|
936
|
-
lines.push('No daemon capability gaps matched this query.');
|
|
937
|
-
return lines.join('\n');
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
for (const gap of gaps) {
|
|
941
|
-
lines.push(`${gap.title} [${gap.severity}; ${gap.kind}]`);
|
|
942
|
-
if (gap.areaId) lines.push(` area: ${gap.areaId}`);
|
|
943
|
-
lines.push(` detail: ${gap.detail}`);
|
|
944
|
-
lines.push(` action: ${gap.action}`);
|
|
945
|
-
lines.push('');
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
return lines.join('\n').trimEnd();
|
|
949
|
-
}
|
|
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
|
-
|
|
1123
|
-
export function renderDaemonCapabilityAudit(
|
|
1124
|
-
audit: DaemonCapabilityAuditSuccess,
|
|
1125
|
-
areas: readonly DaemonCapabilityAuditArea[] = audit.areas,
|
|
1126
|
-
): string {
|
|
1127
|
-
const lines: string[] = [
|
|
1128
|
-
'GoodVibes daemon capability audit',
|
|
1129
|
-
` daemon: ${audit.baseUrl}`,
|
|
1130
|
-
` SDK: Agent expects ${audit.expectedSdkVersion}; daemon reports ${audit.daemonVersion}`,
|
|
1131
|
-
` compatibility: ${audit.daemonCompatible ? 'matched' : 'mismatch'}`,
|
|
1132
|
-
` method catalog: ${audit.methodCount} methods from ${audit.methodCatalogRoute}`,
|
|
1133
|
-
` Agent Knowledge: ${audit.agentKnowledgeRouteReady ? 'ready' : 'missing'} ${audit.agentKnowledgeRoute}`,
|
|
1134
|
-
' isolation: default Knowledge/Wiki fallback no; HomeGraph fallback no',
|
|
1135
|
-
'',
|
|
1136
|
-
];
|
|
1137
|
-
|
|
1138
|
-
for (const warning of audit.warnings) lines.push(` warning: ${warning}`);
|
|
1139
|
-
if (audit.warnings.length > 0) lines.push('');
|
|
1140
|
-
|
|
1141
|
-
for (const area of areas) {
|
|
1142
|
-
const requiredTotal = area.presentRequiredMethodIds.length + area.missingRequiredMethodIds.length;
|
|
1143
|
-
const optionalTotal = area.presentOptionalMethodIds.length + area.missingOptionalMethodIds.length;
|
|
1144
|
-
lines.push(`${area.title} [${area.coverage}]`);
|
|
1145
|
-
lines.push(` baseline: ${area.competitorBaseline}`);
|
|
1146
|
-
lines.push(` Agent use: ${area.agentUse}`);
|
|
1147
|
-
lines.push(` required methods: ${area.presentRequiredMethodIds.length}/${requiredTotal}`);
|
|
1148
|
-
if (area.missingRequiredMethodIds.length > 0) lines.push(` missing required: ${area.missingRequiredMethodIds.join(', ')}`);
|
|
1149
|
-
if (optionalTotal > 0) {
|
|
1150
|
-
lines.push(` optional methods: ${area.presentOptionalMethodIds.length}/${optionalTotal}`);
|
|
1151
|
-
if (area.missingOptionalMethodIds.length > 0) lines.push(` missing optional: ${area.missingOptionalMethodIds.join(', ')}`);
|
|
1152
|
-
}
|
|
1153
|
-
lines.push(` route risk: ${area.routeRisk.readOnlyMethodCount} read-only; ${area.routeRisk.mutatingMethodCount} mutating; ${area.routeRisk.dangerousMethodIds.length} dangerous; ${area.routeRisk.authenticatedMethodCount} authenticated`);
|
|
1154
|
-
if (area.routeRisk.dangerousMethodIds.length > 0) {
|
|
1155
|
-
lines.push(` dangerous methods: ${area.routeRisk.dangerousMethodIds.join(', ')}`);
|
|
1156
|
-
}
|
|
1157
|
-
for (const route of area.agentRoutes) lines.push(` route: ${route.route} [${route.coverage}]`);
|
|
1158
|
-
lines.push(` next: ${area.next.join(' | ')}`);
|
|
1159
|
-
lines.push('');
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
return lines.join('\n').trimEnd();
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
export function renderDaemonCapabilityFailure(failure: DaemonCapabilityAuditFailure): string {
|
|
1166
|
-
const details = [
|
|
1167
|
-
`GoodVibes daemon capability audit failed [${failure.kind}]`,
|
|
1168
|
-
` daemon: ${failure.baseUrl}`,
|
|
1169
|
-
` route: ${failure.route}`,
|
|
1170
|
-
` error: ${failure.error}`,
|
|
1171
|
-
];
|
|
1172
|
-
if (failure.daemonVersion || failure.expectedSdkVersion) {
|
|
1173
|
-
details.push(` SDK: Agent expects ${failure.expectedSdkVersion ?? SDK_VERSION}; daemon reports ${failure.daemonVersion ?? 'unknown'}`);
|
|
1174
|
-
}
|
|
1175
|
-
if (failure.kind === 'auth_required') details.push(' next: authenticate the Agent against the existing GoodVibes daemon; no token value was printed.');
|
|
1176
|
-
if (failure.kind === 'daemon_unavailable') details.push(' next: start or reconnect the external GoodVibes daemon; Agent will not start it.');
|
|
1177
|
-
if (failure.kind === 'version_mismatch') details.push(' next: update/restart the externally owned daemon to match the Agent SDK pin.');
|
|
1178
|
-
if (failure.kind === 'daemon_route_unavailable') details.push(' next: verify the external daemon exposes the published SDK/operator routes.');
|
|
1179
|
-
return details.join('\n');
|
|
1180
|
-
}
|