@pellux/goodvibes-agent 0.1.89 → 0.1.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/cli/agent-knowledge-command.ts +212 -144
- package/src/cli/agent-knowledge-format.ts +325 -0
- package/src/cli/help.ts +9 -0
- package/src/cli/package-verification.ts +35 -0
- package/src/input/agent-workspace-setup.ts +1 -1
- package/src/input/commands/agent-skills-runtime.ts +5 -1
- package/src/input/commands/health-runtime.ts +12 -15
- package/src/input/commands/mcp-runtime.ts +2 -2
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/panels/approval-panel.ts +1 -1
- package/src/panels/automation-control-panel.ts +1 -1
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-health-domains.ts +9 -9
- package/src/panels/session-browser-panel.ts +1 -1
- package/src/panels/session-maintenance.ts +3 -3
- package/src/panels/subscription-panel.ts +1 -1
- package/src/renderer/help-overlay.ts +38 -14
- package/src/renderer/settings-modal.ts +1 -1
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- 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.91 - 2026-06-01
|
|
6
|
+
|
|
7
|
+
- efbb82a Expand Agent Knowledge CLI management
|
|
8
|
+
|
|
9
|
+
## 0.1.90 - 2026-06-01
|
|
10
|
+
|
|
11
|
+
- 19f67ea Polish Agent command guidance
|
|
12
|
+
|
|
5
13
|
## 0.1.89 - 2026-06-01
|
|
6
14
|
|
|
7
15
|
- 6be201c Fix Agent workspace command targets
|
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ goodvibes-agent --help
|
|
|
17
17
|
goodvibes-agent status
|
|
18
18
|
goodvibes-agent profiles templates
|
|
19
19
|
goodvibes-agent knowledge status
|
|
20
|
+
goodvibes-agent knowledge list --kind sources
|
|
21
|
+
goodvibes-agent knowledge import-urls ./agent-sources.txt --yes
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
If `goodvibes-agent` is not found after installation, add Bun's global bin directory to `PATH`:
|
|
@@ -95,6 +97,8 @@ GoodVibes Agent owns the operator assistant TUI: serial assistant flow, proactiv
|
|
|
95
97
|
|
|
96
98
|
Agent Knowledge/Wiki is its own product segment. Agent uses `/api/goodvibes-agent/knowledge/*` and must not fall back to default Knowledge/Wiki or other product-specific knowledge routes.
|
|
97
99
|
|
|
100
|
+
Agent Knowledge CLI commands can ask/search, inspect sources/nodes/issues, inspect connectors, ingest a URL, import URL/bookmark files, and reindex the Agent segment. Confirmed mutations require `--yes`.
|
|
101
|
+
|
|
98
102
|
GoodVibes TUI owns coding execution: file edits, git/worktree workflows, coding panels, execution isolation UX, and WRFC execution. Agent may delegate explicit build/fix/review work to TUI through public runtime/session contracts; normal assistant chat must not use shared coding sessions.
|
|
99
103
|
|
|
100
104
|
## Package Docs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.91",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
|
|
6
6
|
"type": "module",
|
|
@@ -5,6 +5,19 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
5
5
|
import { SDK_VERSION, VERSION } from '../version.ts';
|
|
6
6
|
import type { CliCommandOutput } from './types.ts';
|
|
7
7
|
import type { CliCommandRuntime } from './management.ts';
|
|
8
|
+
import {
|
|
9
|
+
formatAsk,
|
|
10
|
+
formatBatchIngest,
|
|
11
|
+
formatConnectors,
|
|
12
|
+
formatEntityList,
|
|
13
|
+
formatFailure,
|
|
14
|
+
formatIngest,
|
|
15
|
+
formatItem,
|
|
16
|
+
formatMap,
|
|
17
|
+
formatReindex,
|
|
18
|
+
formatSearch,
|
|
19
|
+
formatStatus,
|
|
20
|
+
} from './agent-knowledge-format.ts';
|
|
8
21
|
import { formatJsonOrText, yesNo } from './management.ts';
|
|
9
22
|
|
|
10
23
|
type JsonRecord = Record<string, unknown>;
|
|
@@ -52,10 +65,46 @@ const AGENT_KNOWLEDGE_METHODS = {
|
|
|
52
65
|
kind: 'agentKnowledge.search',
|
|
53
66
|
route: '/api/goodvibes-agent/knowledge/search',
|
|
54
67
|
},
|
|
68
|
+
sourcesList: {
|
|
69
|
+
kind: 'agentKnowledge.sources.list',
|
|
70
|
+
route: '/api/goodvibes-agent/knowledge/sources',
|
|
71
|
+
},
|
|
72
|
+
nodesList: {
|
|
73
|
+
kind: 'agentKnowledge.nodes.list',
|
|
74
|
+
route: '/api/goodvibes-agent/knowledge/nodes',
|
|
75
|
+
},
|
|
76
|
+
issuesList: {
|
|
77
|
+
kind: 'agentKnowledge.issues.list',
|
|
78
|
+
route: '/api/goodvibes-agent/knowledge/issues',
|
|
79
|
+
},
|
|
80
|
+
itemGet: {
|
|
81
|
+
kind: 'agentKnowledge.item.get',
|
|
82
|
+
route: '/api/goodvibes-agent/knowledge/items/{id}',
|
|
83
|
+
},
|
|
84
|
+
map: {
|
|
85
|
+
kind: 'agentKnowledge.map',
|
|
86
|
+
route: '/api/goodvibes-agent/knowledge/map',
|
|
87
|
+
},
|
|
88
|
+
connectorsList: {
|
|
89
|
+
kind: 'agentKnowledge.connectors.list',
|
|
90
|
+
route: '/api/goodvibes-agent/knowledge/connectors',
|
|
91
|
+
},
|
|
55
92
|
ingestUrl: {
|
|
56
93
|
kind: 'agentKnowledge.ingest.url',
|
|
57
94
|
route: '/api/goodvibes-agent/knowledge/ingest/url',
|
|
58
95
|
},
|
|
96
|
+
ingestUrls: {
|
|
97
|
+
kind: 'agentKnowledge.ingest.urls',
|
|
98
|
+
route: '/api/goodvibes-agent/knowledge/ingest/urls',
|
|
99
|
+
},
|
|
100
|
+
ingestBookmarks: {
|
|
101
|
+
kind: 'agentKnowledge.ingest.bookmarks',
|
|
102
|
+
route: '/api/goodvibes-agent/knowledge/ingest/bookmarks',
|
|
103
|
+
},
|
|
104
|
+
reindex: {
|
|
105
|
+
kind: 'agentKnowledge.reindex',
|
|
106
|
+
route: '/api/goodvibes-agent/knowledge/reindex',
|
|
107
|
+
},
|
|
59
108
|
} as const;
|
|
60
109
|
|
|
61
110
|
const DELEGATION_METHOD = {
|
|
@@ -79,25 +128,6 @@ function readString(record: JsonRecord | null, key: string): string | null {
|
|
|
79
128
|
return typeof value === 'string' ? value : null;
|
|
80
129
|
}
|
|
81
130
|
|
|
82
|
-
function readNumber(record: JsonRecord | null, key: string): number | null {
|
|
83
|
-
const value = record?.[key];
|
|
84
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function readBoolean(record: JsonRecord | null, key: string): boolean | null {
|
|
88
|
-
const value = record?.[key];
|
|
89
|
-
return typeof value === 'boolean' ? value : null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function readArray(record: JsonRecord | null, key: string): readonly unknown[] {
|
|
93
|
-
const value = record?.[key];
|
|
94
|
-
return Array.isArray(value) ? value : [];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function cleanInline(value: unknown): string {
|
|
98
|
-
return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
99
|
-
}
|
|
100
|
-
|
|
101
131
|
function commandValues(args: readonly string[]): string[] {
|
|
102
132
|
const values: string[] = [];
|
|
103
133
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -265,6 +295,50 @@ async function postAgentKnowledgeJson<TData>(
|
|
|
265
295
|
return parsed as TData;
|
|
266
296
|
}
|
|
267
297
|
|
|
298
|
+
function queryRoute(route: string, query: JsonRecord): string {
|
|
299
|
+
const params = new URLSearchParams();
|
|
300
|
+
for (const [key, value] of Object.entries(query)) {
|
|
301
|
+
if (value === undefined || value === null || value === '') continue;
|
|
302
|
+
if (Array.isArray(value)) {
|
|
303
|
+
for (const item of value) {
|
|
304
|
+
if (typeof item === 'string' && item.trim().length > 0) params.append(key, item);
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
309
|
+
params.set(key, String(value));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const suffix = params.toString();
|
|
313
|
+
return suffix ? `${route}?${suffix}` : route;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function getAgentKnowledgeJson<TData>(
|
|
317
|
+
connection: AgentDaemonConnection,
|
|
318
|
+
route: string,
|
|
319
|
+
query: JsonRecord = {},
|
|
320
|
+
): Promise<TData> {
|
|
321
|
+
const response = await fetch(`${connection.baseUrl}${queryRoute(route, query)}`, {
|
|
322
|
+
headers: {
|
|
323
|
+
authorization: `Bearer ${connection.token ?? ''}`,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
const text = await response.text();
|
|
327
|
+
let parsed: unknown = text;
|
|
328
|
+
if (text.trim()) {
|
|
329
|
+
try {
|
|
330
|
+
parsed = JSON.parse(text) as unknown;
|
|
331
|
+
} catch {
|
|
332
|
+
parsed = text;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!response.ok) {
|
|
336
|
+
const detail = isRecord(parsed) && typeof parsed.error === 'string' ? parsed.error : text;
|
|
337
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}${detail ? `: ${detail}` : ''}`);
|
|
338
|
+
}
|
|
339
|
+
return parsed as TData;
|
|
340
|
+
}
|
|
341
|
+
|
|
268
342
|
function findDisallowedKnowledgeScopeFlag(args: readonly string[]): string | null {
|
|
269
343
|
const disallowed = [
|
|
270
344
|
'--space',
|
|
@@ -292,111 +366,6 @@ function formatScopeFlagRejection(flag: string): string {
|
|
|
292
366
|
].join('\n');
|
|
293
367
|
}
|
|
294
368
|
|
|
295
|
-
function sourceLine(value: unknown): string {
|
|
296
|
-
const record = isRecord(value) ? value : {};
|
|
297
|
-
const title = cleanInline(record.title)
|
|
298
|
-
|| cleanInline(record.canonicalUri)
|
|
299
|
-
|| cleanInline(record.sourceUri)
|
|
300
|
-
|| cleanInline(record.url)
|
|
301
|
-
|| cleanInline(record.id)
|
|
302
|
-
|| 'untitled';
|
|
303
|
-
const type = cleanInline(record.sourceType) || cleanInline(record.type) || 'source';
|
|
304
|
-
const url = cleanInline(record.canonicalUri) || cleanInline(record.sourceUri) || cleanInline(record.url);
|
|
305
|
-
return url && url !== title ? `[${type}] ${title} (${url})` : `[${type}] ${title}`;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function resultLine(value: unknown): string {
|
|
309
|
-
const record = isRecord(value) ? value : {};
|
|
310
|
-
const title = cleanInline(record.title) || cleanInline(record.id) || 'untitled';
|
|
311
|
-
const id = cleanInline(record.id);
|
|
312
|
-
const type = cleanInline(record.type) || cleanInline(record.kind) || cleanInline(record.sourceType) || 'result';
|
|
313
|
-
const score = readNumber(record, 'score');
|
|
314
|
-
const url = cleanInline(record.url) || cleanInline(record.canonicalUri) || cleanInline(record.sourceUri);
|
|
315
|
-
const snippet = cleanInline(record.snippet) || cleanInline(record.summary) || cleanInline(record.text);
|
|
316
|
-
const parts = [
|
|
317
|
-
`[${type}] ${title}`,
|
|
318
|
-
id && id !== title ? `id=${id}` : '',
|
|
319
|
-
score !== null ? `score=${score.toFixed(3)}` : '',
|
|
320
|
-
url ? `url=${url}` : '',
|
|
321
|
-
].filter((part) => part.length > 0);
|
|
322
|
-
return snippet ? `${parts.join(' ')}\n ${snippet}` : parts.join(' ');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function formatStatus(data: unknown): string {
|
|
326
|
-
const record = isRecord(data) ? data : {};
|
|
327
|
-
const ready = readBoolean(record, 'ready');
|
|
328
|
-
const sourceCount = readNumber(record, 'sourceCount');
|
|
329
|
-
const nodeCount = readNumber(record, 'nodeCount');
|
|
330
|
-
const issueCount = readNumber(record, 'issueCount');
|
|
331
|
-
const edgeCount = readNumber(record, 'edgeCount');
|
|
332
|
-
const storagePath = readString(record, 'storagePath');
|
|
333
|
-
return [
|
|
334
|
-
'Agent Knowledge status',
|
|
335
|
-
` ready: ${ready === null ? 'unknown' : yesNo(ready)}`,
|
|
336
|
-
` sources: ${sourceCount ?? 'unknown'}`,
|
|
337
|
-
` nodes: ${nodeCount ?? 'unknown'}`,
|
|
338
|
-
` edges: ${edgeCount ?? 'unknown'}`,
|
|
339
|
-
` issues: ${issueCount ?? 'unknown'}`,
|
|
340
|
-
storagePath ? ` storage: ${storagePath}` : null,
|
|
341
|
-
' route: /api/goodvibes-agent/knowledge/status',
|
|
342
|
-
].filter((line): line is string => Boolean(line)).join('\n');
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function formatAsk(data: unknown, query: string): string {
|
|
346
|
-
const record = isRecord(data) ? data : {};
|
|
347
|
-
const answer = isRecord(record.answer) ? record.answer : record;
|
|
348
|
-
const text = cleanInline(answer.text) || cleanInline(record.answer) || 'No answer returned.';
|
|
349
|
-
const confidence = readNumber(answer, 'confidence') ?? readNumber(record, 'confidence');
|
|
350
|
-
const synthesized = readBoolean(answer, 'synthesized');
|
|
351
|
-
const sources = readArray(answer, 'sources');
|
|
352
|
-
const facts = readArray(answer, 'facts');
|
|
353
|
-
const gaps = readArray(answer, 'gaps');
|
|
354
|
-
const lines = [
|
|
355
|
-
`Agent Knowledge answer: ${query}`,
|
|
356
|
-
text,
|
|
357
|
-
'',
|
|
358
|
-
`confidence: ${confidence ?? 'unknown'}${synthesized === null ? '' : ` synthesized: ${yesNo(synthesized)}`}`,
|
|
359
|
-
];
|
|
360
|
-
if (sources.length > 0) {
|
|
361
|
-
lines.push('', 'Sources:', ...sources.slice(0, 8).map((source) => ` - ${sourceLine(source)}`));
|
|
362
|
-
}
|
|
363
|
-
if (facts.length > 0) lines.push('', `Facts: ${facts.length}`);
|
|
364
|
-
if (gaps.length > 0) lines.push('', `Gaps: ${gaps.length}`);
|
|
365
|
-
return lines.join('\n');
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function formatSearch(data: unknown, query: string): string {
|
|
369
|
-
const record = isRecord(data) ? data : {};
|
|
370
|
-
const items = readArray(record, 'items');
|
|
371
|
-
const results = items.length > 0 ? items : readArray(record, 'results');
|
|
372
|
-
if (results.length === 0) {
|
|
373
|
-
return [
|
|
374
|
-
`Agent Knowledge search: ${query}`,
|
|
375
|
-
' no results',
|
|
376
|
-
' route: /api/goodvibes-agent/knowledge/search',
|
|
377
|
-
].join('\n');
|
|
378
|
-
}
|
|
379
|
-
return [
|
|
380
|
-
`Agent Knowledge search: ${query}`,
|
|
381
|
-
...results.slice(0, 10).map((result, index) => ` ${index + 1}. ${resultLine(result)}`),
|
|
382
|
-
].join('\n');
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function formatIngest(data: unknown, url: string): string {
|
|
386
|
-
const record = isRecord(data) ? data : {};
|
|
387
|
-
const source = isRecord(record.source) ? record.source : record;
|
|
388
|
-
const sourceId = cleanInline(source.id);
|
|
389
|
-
const canonicalUri = cleanInline(source.canonicalUri) || cleanInline(source.sourceUri) || url;
|
|
390
|
-
const artifactId = cleanInline(record.artifactId);
|
|
391
|
-
return [
|
|
392
|
-
'Agent Knowledge ingest-url accepted',
|
|
393
|
-
` source: ${sourceId || '(pending)'}`,
|
|
394
|
-
` url: ${canonicalUri}`,
|
|
395
|
-
artifactId ? ` artifact: ${artifactId}` : null,
|
|
396
|
-
' route: /api/goodvibes-agent/knowledge/ingest/url',
|
|
397
|
-
].filter((line): line is string => Boolean(line)).join('\n');
|
|
398
|
-
}
|
|
399
|
-
|
|
400
369
|
function buildDelegationBody(task: string, wrfcRequested: boolean): string {
|
|
401
370
|
return [
|
|
402
371
|
'GoodVibes Agent explicit build delegation.',
|
|
@@ -414,25 +383,6 @@ function buildDelegationBody(task: string, wrfcRequested: boolean): string {
|
|
|
414
383
|
].join('\n');
|
|
415
384
|
}
|
|
416
385
|
|
|
417
|
-
function formatFailure(failure: AgentKnowledgeFailure, json: boolean): string {
|
|
418
|
-
if (json) return JSON.stringify(failure, null, 2);
|
|
419
|
-
return [
|
|
420
|
-
`Agent Knowledge error: ${failure.kind}`,
|
|
421
|
-
` ${failure.error}`,
|
|
422
|
-
` runtime: ${failure.baseUrl}`,
|
|
423
|
-
` route: ${failure.route}`,
|
|
424
|
-
failure.kind === 'version_mismatch' && failure.daemonVersion && failure.expectedSdkVersion
|
|
425
|
-
? ` versions: runtime=${failure.daemonVersion} expected=${failure.expectedSdkVersion}`
|
|
426
|
-
: null,
|
|
427
|
-
failure.kind === 'version_mismatch'
|
|
428
|
-
? ' next: update/restart the external GoodVibes runtime so /status matches the Agent SDK pin.'
|
|
429
|
-
: null,
|
|
430
|
-
failure.kind === 'daemon_route_unavailable'
|
|
431
|
-
? ' next: update/restart the external GoodVibes runtime to the SDK version required by this Agent package.'
|
|
432
|
-
: null,
|
|
433
|
-
].filter((line): line is string => Boolean(line)).join('\n');
|
|
434
|
-
}
|
|
435
|
-
|
|
436
386
|
async function runKnowledgeCall<TData>(
|
|
437
387
|
runtime: CliCommandRuntime,
|
|
438
388
|
method: DaemonCallMethod,
|
|
@@ -524,6 +474,63 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
524
474
|
};
|
|
525
475
|
}
|
|
526
476
|
|
|
477
|
+
if (normalized === 'list' || normalized === 'sources' || normalized === 'nodes' || normalized === 'issues') {
|
|
478
|
+
const requestedKind = normalized === 'list' ? (readOptionValue(rest, '--kind') ?? 'sources').toLowerCase() : normalized;
|
|
479
|
+
const kind = requestedKind === 'nodes' || requestedKind === 'issues' ? requestedKind : 'sources';
|
|
480
|
+
const limit = readPositiveInt(rest, '--limit', 10);
|
|
481
|
+
const method = kind === 'sources'
|
|
482
|
+
? AGENT_KNOWLEDGE_METHODS.sourcesList
|
|
483
|
+
: kind === 'nodes'
|
|
484
|
+
? AGENT_KNOWLEDGE_METHODS.nodesList
|
|
485
|
+
: AGENT_KNOWLEDGE_METHODS.issuesList;
|
|
486
|
+
const result = await runKnowledgeCall(runtime, method, async (connection) => (
|
|
487
|
+
await getAgentKnowledgeJson(connection, method.route, { limit })
|
|
488
|
+
));
|
|
489
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
490
|
+
return {
|
|
491
|
+
output: formatJsonOrText(runtime.cli)(result, formatEntityList(result.data, kind, limit)),
|
|
492
|
+
exitCode: 0,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (normalized === 'get') {
|
|
497
|
+
const [id] = commandValues(rest);
|
|
498
|
+
if (!id) return { output: 'Usage: goodvibes-agent knowledge get <source|node|issue id>', exitCode: 2 };
|
|
499
|
+
const route = `/api/goodvibes-agent/knowledge/items/${encodeURIComponent(id)}`;
|
|
500
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.itemGet, async (connection) => (
|
|
501
|
+
await getAgentKnowledgeJson(connection, route)
|
|
502
|
+
));
|
|
503
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
504
|
+
return {
|
|
505
|
+
output: formatJsonOrText(runtime.cli)(result, formatItem(result.data, id)),
|
|
506
|
+
exitCode: 0,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (normalized === 'map') {
|
|
511
|
+
const limit = readPositiveInt(rest, '--limit', 50);
|
|
512
|
+
const query = commandValues(rest).join(' ').trim();
|
|
513
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.map, async (connection) => (
|
|
514
|
+
await getAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.map.route, { limit, query })
|
|
515
|
+
));
|
|
516
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
517
|
+
return {
|
|
518
|
+
output: formatJsonOrText(runtime.cli)(result, formatMap(result.data)),
|
|
519
|
+
exitCode: 0,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (normalized === 'connectors') {
|
|
524
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorsList, async (connection) => (
|
|
525
|
+
await getAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.connectorsList.route)
|
|
526
|
+
));
|
|
527
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
528
|
+
return {
|
|
529
|
+
output: formatJsonOrText(runtime.cli)(result, formatConnectors(result.data)),
|
|
530
|
+
exitCode: 0,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
527
534
|
if (normalized === 'ingest-url') {
|
|
528
535
|
const values = commandValues(rest);
|
|
529
536
|
const url = values[0];
|
|
@@ -558,8 +565,69 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
558
565
|
};
|
|
559
566
|
}
|
|
560
567
|
|
|
568
|
+
if (normalized === 'import-urls' || normalized === 'import-bookmarks') {
|
|
569
|
+
const values = commandValues(rest);
|
|
570
|
+
const path = values[0];
|
|
571
|
+
const method = normalized === 'import-urls'
|
|
572
|
+
? AGENT_KNOWLEDGE_METHODS.ingestUrls
|
|
573
|
+
: AGENT_KNOWLEDGE_METHODS.ingestBookmarks;
|
|
574
|
+
if (!path) {
|
|
575
|
+
return {
|
|
576
|
+
output: `Usage: goodvibes-agent knowledge ${normalized} <path> [--allow-private-hosts] --yes`,
|
|
577
|
+
exitCode: 2,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
if (!confirmation.present) {
|
|
581
|
+
const failure = {
|
|
582
|
+
ok: false,
|
|
583
|
+
kind: 'confirmation_required',
|
|
584
|
+
error: `Refusing to import ${path} into Agent Knowledge without --yes.`,
|
|
585
|
+
route: method.route,
|
|
586
|
+
};
|
|
587
|
+
return {
|
|
588
|
+
output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge ${normalized} <path> [--allow-private-hosts] --yes`,
|
|
589
|
+
exitCode: 2,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
const result = await runKnowledgeCall(runtime, method, async (connection) => (
|
|
593
|
+
await postAgentKnowledgeJson(connection, method.route, {
|
|
594
|
+
path,
|
|
595
|
+
allowPrivateHosts: hasFlag(rest, '--allow-private-hosts'),
|
|
596
|
+
metadata: { originSurface: 'goodvibes-agent-cli' },
|
|
597
|
+
})
|
|
598
|
+
));
|
|
599
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
600
|
+
return {
|
|
601
|
+
output: formatJsonOrText(runtime.cli)(result, formatBatchIngest(result.data, normalized)),
|
|
602
|
+
exitCode: 0,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (normalized === 'reindex') {
|
|
607
|
+
if (!confirmation.present) {
|
|
608
|
+
const failure = {
|
|
609
|
+
ok: false,
|
|
610
|
+
kind: 'confirmation_required',
|
|
611
|
+
error: 'Refusing to reindex Agent Knowledge without --yes.',
|
|
612
|
+
route: AGENT_KNOWLEDGE_METHODS.reindex.route,
|
|
613
|
+
};
|
|
614
|
+
return {
|
|
615
|
+
output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge reindex --yes`,
|
|
616
|
+
exitCode: 2,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.reindex, async (connection) => (
|
|
620
|
+
await postAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.reindex.route, {})
|
|
621
|
+
));
|
|
622
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
623
|
+
return {
|
|
624
|
+
output: formatJsonOrText(runtime.cli)(result, formatReindex(result.data)),
|
|
625
|
+
exitCode: 0,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
561
629
|
return {
|
|
562
|
-
output: 'Usage: goodvibes-agent knowledge [status|ask <question>|search <query>|ingest-url <url> --yes]',
|
|
630
|
+
output: 'Usage: goodvibes-agent knowledge [status|ask <question>|search <query>|list|sources|nodes|issues|get <id>|map|connectors|ingest-url <url> --yes|import-urls <path> --yes|import-bookmarks <path> --yes|reindex --yes]',
|
|
563
631
|
exitCode: 2,
|
|
564
632
|
};
|
|
565
633
|
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
type JsonRecord = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export interface AgentKnowledgeFailureLike {
|
|
4
|
+
readonly ok: false;
|
|
5
|
+
readonly kind: string;
|
|
6
|
+
readonly error: string;
|
|
7
|
+
readonly baseUrl: string;
|
|
8
|
+
readonly route: string;
|
|
9
|
+
readonly daemonVersion?: string;
|
|
10
|
+
readonly expectedSdkVersion?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isRecord(value: unknown): value is JsonRecord {
|
|
14
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readString(record: JsonRecord | null, key: string): string | null {
|
|
18
|
+
const value = record?.[key];
|
|
19
|
+
return typeof value === 'string' ? value : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readNumber(record: JsonRecord | null, key: string): number | null {
|
|
23
|
+
const value = record?.[key];
|
|
24
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readBoolean(record: JsonRecord | null, key: string): boolean | null {
|
|
28
|
+
const value = record?.[key];
|
|
29
|
+
return typeof value === 'boolean' ? value : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readArray(record: JsonRecord | null, key: string): readonly unknown[] {
|
|
33
|
+
const value = record?.[key];
|
|
34
|
+
return Array.isArray(value) ? value : [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cleanInline(value: unknown): string {
|
|
38
|
+
return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function yesNo(value: boolean): string {
|
|
42
|
+
return value ? 'yes' : 'no';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sourceLine(value: unknown): string {
|
|
46
|
+
const record = isRecord(value) ? value : {};
|
|
47
|
+
const title = cleanInline(record.title)
|
|
48
|
+
|| cleanInline(record.canonicalUri)
|
|
49
|
+
|| cleanInline(record.sourceUri)
|
|
50
|
+
|| cleanInline(record.url)
|
|
51
|
+
|| cleanInline(record.id)
|
|
52
|
+
|| 'untitled';
|
|
53
|
+
const type = cleanInline(record.sourceType) || cleanInline(record.type) || 'source';
|
|
54
|
+
const url = cleanInline(record.canonicalUri) || cleanInline(record.sourceUri) || cleanInline(record.url);
|
|
55
|
+
return url && url !== title ? `[${type}] ${title} (${url})` : `[${type}] ${title}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resultLine(value: unknown): string {
|
|
59
|
+
const record = isRecord(value) ? value : {};
|
|
60
|
+
const title = cleanInline(record.title) || cleanInline(record.id) || 'untitled';
|
|
61
|
+
const id = cleanInline(record.id);
|
|
62
|
+
const type = cleanInline(record.type) || cleanInline(record.kind) || cleanInline(record.sourceType) || 'result';
|
|
63
|
+
const score = readNumber(record, 'score');
|
|
64
|
+
const url = cleanInline(record.url) || cleanInline(record.canonicalUri) || cleanInline(record.sourceUri);
|
|
65
|
+
const snippet = cleanInline(record.snippet) || cleanInline(record.summary) || cleanInline(record.text);
|
|
66
|
+
const parts = [
|
|
67
|
+
`[${type}] ${title}`,
|
|
68
|
+
id && id !== title ? `id=${id}` : '',
|
|
69
|
+
score !== null ? `score=${score.toFixed(3)}` : '',
|
|
70
|
+
url ? `url=${url}` : '',
|
|
71
|
+
].filter((part) => part.length > 0);
|
|
72
|
+
return snippet ? `${parts.join(' ')}\n ${snippet}` : parts.join(' ');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function nodeLine(value: unknown): string {
|
|
76
|
+
const record = isRecord(value) ? value : {};
|
|
77
|
+
const id = cleanInline(record.id);
|
|
78
|
+
const kind = cleanInline(record.kind) || 'node';
|
|
79
|
+
const title = cleanInline(record.title) || id || 'untitled';
|
|
80
|
+
const confidence = readNumber(record, 'confidence');
|
|
81
|
+
const status = cleanInline(record.status);
|
|
82
|
+
const summary = cleanInline(record.summary);
|
|
83
|
+
const parts = [
|
|
84
|
+
`[${kind}] ${title}`,
|
|
85
|
+
id && id !== title ? `id=${id}` : '',
|
|
86
|
+
status ? `status=${status}` : '',
|
|
87
|
+
confidence !== null ? `confidence=${confidence}` : '',
|
|
88
|
+
].filter((part) => part.length > 0);
|
|
89
|
+
return summary ? `${parts.join(' ')}\n ${summary}` : parts.join(' ');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function issueLine(value: unknown): string {
|
|
93
|
+
const record = isRecord(value) ? value : {};
|
|
94
|
+
const id = cleanInline(record.id) || 'issue';
|
|
95
|
+
const severity = cleanInline(record.severity) || 'unknown';
|
|
96
|
+
const code = cleanInline(record.code) || 'issue';
|
|
97
|
+
const status = cleanInline(record.status);
|
|
98
|
+
const message = cleanInline(record.message);
|
|
99
|
+
const suffix = status ? ` status=${status}` : '';
|
|
100
|
+
return ` - ${id} [${severity}] ${code}${suffix}${message ? ` - ${message}` : ''}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function connectorLine(value: unknown): string {
|
|
104
|
+
const record = isRecord(value) ? value : {};
|
|
105
|
+
const id = cleanInline(record.id) || 'connector';
|
|
106
|
+
const name = cleanInline(record.displayName) || id;
|
|
107
|
+
const sourceType = cleanInline(record.sourceType);
|
|
108
|
+
const description = cleanInline(record.description);
|
|
109
|
+
const suffix = sourceType ? ` sourceType=${sourceType}` : '';
|
|
110
|
+
return description ? ` - ${id} ${name}${suffix}\n ${description}` : ` - ${id} ${name}${suffix}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function formatStatus(data: unknown): string {
|
|
114
|
+
const record = isRecord(data) ? data : {};
|
|
115
|
+
const ready = readBoolean(record, 'ready');
|
|
116
|
+
const sourceCount = readNumber(record, 'sourceCount');
|
|
117
|
+
const nodeCount = readNumber(record, 'nodeCount');
|
|
118
|
+
const issueCount = readNumber(record, 'issueCount');
|
|
119
|
+
const edgeCount = readNumber(record, 'edgeCount');
|
|
120
|
+
const storagePath = readString(record, 'storagePath');
|
|
121
|
+
return [
|
|
122
|
+
'Agent Knowledge status',
|
|
123
|
+
` ready: ${ready === null ? 'unknown' : yesNo(ready)}`,
|
|
124
|
+
` sources: ${sourceCount ?? 'unknown'}`,
|
|
125
|
+
` nodes: ${nodeCount ?? 'unknown'}`,
|
|
126
|
+
` edges: ${edgeCount ?? 'unknown'}`,
|
|
127
|
+
` issues: ${issueCount ?? 'unknown'}`,
|
|
128
|
+
storagePath ? ` storage: ${storagePath}` : null,
|
|
129
|
+
' route: /api/goodvibes-agent/knowledge/status',
|
|
130
|
+
].filter((line): line is string => Boolean(line)).join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function formatEntityList(data: unknown, kind: 'sources' | 'nodes' | 'issues', limit: number): string {
|
|
134
|
+
const record = isRecord(data) ? data : {};
|
|
135
|
+
const values = readArray(record, kind);
|
|
136
|
+
if (values.length === 0) {
|
|
137
|
+
return [
|
|
138
|
+
`Agent Knowledge ${kind}`,
|
|
139
|
+
' no records',
|
|
140
|
+
` route: /api/goodvibes-agent/knowledge/${kind}`,
|
|
141
|
+
].join('\n');
|
|
142
|
+
}
|
|
143
|
+
const format = kind === 'sources'
|
|
144
|
+
? sourceLine
|
|
145
|
+
: kind === 'nodes'
|
|
146
|
+
? nodeLine
|
|
147
|
+
: issueLine;
|
|
148
|
+
return [
|
|
149
|
+
`Agent Knowledge ${kind} (${values.length}, limit ${limit})`,
|
|
150
|
+
...values.slice(0, limit).map((value, index) => (
|
|
151
|
+
kind === 'issues' ? format(value) : ` ${index + 1}. ${format(value)}`
|
|
152
|
+
)),
|
|
153
|
+
].join('\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function formatItem(data: unknown, id: string): string {
|
|
157
|
+
const record = isRecord(data) ? data : {};
|
|
158
|
+
const source = record.source;
|
|
159
|
+
const node = record.node;
|
|
160
|
+
const issue = record.issue;
|
|
161
|
+
const relatedEdges = readArray(record, 'relatedEdges').length;
|
|
162
|
+
const linkedSources = readArray(record, 'linkedSources').length;
|
|
163
|
+
const linkedNodes = readArray(record, 'linkedNodes').length;
|
|
164
|
+
if (source) {
|
|
165
|
+
return [
|
|
166
|
+
`Agent Knowledge item: ${id}`,
|
|
167
|
+
` ${sourceLine(source)}`,
|
|
168
|
+
` relatedEdges=${relatedEdges} linkedSources=${linkedSources} linkedNodes=${linkedNodes}`,
|
|
169
|
+
].join('\n');
|
|
170
|
+
}
|
|
171
|
+
if (node) {
|
|
172
|
+
return [
|
|
173
|
+
`Agent Knowledge item: ${id}`,
|
|
174
|
+
` ${nodeLine(node)}`,
|
|
175
|
+
` relatedEdges=${relatedEdges} linkedSources=${linkedSources} linkedNodes=${linkedNodes}`,
|
|
176
|
+
].join('\n');
|
|
177
|
+
}
|
|
178
|
+
if (issue) {
|
|
179
|
+
return [
|
|
180
|
+
`Agent Knowledge item: ${id}`,
|
|
181
|
+
issueLine(issue),
|
|
182
|
+
` relatedEdges=${relatedEdges} linkedSources=${linkedSources} linkedNodes=${linkedNodes}`,
|
|
183
|
+
].join('\n');
|
|
184
|
+
}
|
|
185
|
+
return [
|
|
186
|
+
`Agent Knowledge item: ${id}`,
|
|
187
|
+
' not found',
|
|
188
|
+
' route: /api/goodvibes-agent/knowledge/items/{id}',
|
|
189
|
+
].join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function formatMap(data: unknown): string {
|
|
193
|
+
const record = isRecord(data) ? data : {};
|
|
194
|
+
const sources = readArray(record, 'sources');
|
|
195
|
+
const nodes = readArray(record, 'nodes');
|
|
196
|
+
const edges = readArray(record, 'edges');
|
|
197
|
+
const issues = readArray(record, 'issues');
|
|
198
|
+
return [
|
|
199
|
+
'Agent Knowledge map',
|
|
200
|
+
` sources: ${sources.length}`,
|
|
201
|
+
` nodes: ${nodes.length}`,
|
|
202
|
+
` edges: ${edges.length}`,
|
|
203
|
+
` issues: ${issues.length}`,
|
|
204
|
+
' route: /api/goodvibes-agent/knowledge/map',
|
|
205
|
+
].join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function formatConnectors(data: unknown): string {
|
|
209
|
+
const record = isRecord(data) ? data : {};
|
|
210
|
+
const connectors = readArray(record, 'connectors');
|
|
211
|
+
if (connectors.length === 0) {
|
|
212
|
+
return [
|
|
213
|
+
'Agent Knowledge connectors',
|
|
214
|
+
' no connectors',
|
|
215
|
+
' route: /api/goodvibes-agent/knowledge/connectors',
|
|
216
|
+
].join('\n');
|
|
217
|
+
}
|
|
218
|
+
return [
|
|
219
|
+
`Agent Knowledge connectors (${connectors.length})`,
|
|
220
|
+
...connectors.map(connectorLine),
|
|
221
|
+
].join('\n');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function formatAsk(data: unknown, query: string): string {
|
|
225
|
+
const record = isRecord(data) ? data : {};
|
|
226
|
+
const answer = isRecord(record.answer) ? record.answer : record;
|
|
227
|
+
const text = cleanInline(answer.text) || cleanInline(record.answer) || 'No answer returned.';
|
|
228
|
+
const confidence = readNumber(answer, 'confidence') ?? readNumber(record, 'confidence');
|
|
229
|
+
const synthesized = readBoolean(answer, 'synthesized');
|
|
230
|
+
const sources = readArray(answer, 'sources');
|
|
231
|
+
const facts = readArray(answer, 'facts');
|
|
232
|
+
const gaps = readArray(answer, 'gaps');
|
|
233
|
+
const lines = [
|
|
234
|
+
`Agent Knowledge answer: ${query}`,
|
|
235
|
+
text,
|
|
236
|
+
'',
|
|
237
|
+
`confidence: ${confidence ?? 'unknown'}${synthesized === null ? '' : ` synthesized: ${yesNo(synthesized)}`}`,
|
|
238
|
+
];
|
|
239
|
+
if (sources.length > 0) {
|
|
240
|
+
lines.push('', 'Sources:', ...sources.slice(0, 8).map((source) => ` - ${sourceLine(source)}`));
|
|
241
|
+
}
|
|
242
|
+
if (facts.length > 0) lines.push('', `Facts: ${facts.length}`);
|
|
243
|
+
if (gaps.length > 0) lines.push('', `Gaps: ${gaps.length}`);
|
|
244
|
+
return lines.join('\n');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function formatSearch(data: unknown, query: string): string {
|
|
248
|
+
const record = isRecord(data) ? data : {};
|
|
249
|
+
const items = readArray(record, 'items');
|
|
250
|
+
const results = items.length > 0 ? items : readArray(record, 'results');
|
|
251
|
+
if (results.length === 0) {
|
|
252
|
+
return [
|
|
253
|
+
`Agent Knowledge search: ${query}`,
|
|
254
|
+
' no results',
|
|
255
|
+
' route: /api/goodvibes-agent/knowledge/search',
|
|
256
|
+
].join('\n');
|
|
257
|
+
}
|
|
258
|
+
return [
|
|
259
|
+
`Agent Knowledge search: ${query}`,
|
|
260
|
+
...results.slice(0, 10).map((result, index) => ` ${index + 1}. ${resultLine(result)}`),
|
|
261
|
+
].join('\n');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function formatIngest(data: unknown, url: string): string {
|
|
265
|
+
const record = isRecord(data) ? data : {};
|
|
266
|
+
const source = isRecord(record.source) ? record.source : record;
|
|
267
|
+
const sourceId = cleanInline(source.id);
|
|
268
|
+
const canonicalUri = cleanInline(source.canonicalUri) || cleanInline(source.sourceUri) || url;
|
|
269
|
+
const artifactId = cleanInline(record.artifactId);
|
|
270
|
+
return [
|
|
271
|
+
'Agent Knowledge ingest-url accepted',
|
|
272
|
+
` source: ${sourceId || '(pending)'}`,
|
|
273
|
+
` url: ${canonicalUri}`,
|
|
274
|
+
artifactId ? ` artifact: ${artifactId}` : null,
|
|
275
|
+
' route: /api/goodvibes-agent/knowledge/ingest/url',
|
|
276
|
+
].filter((line): line is string => Boolean(line)).join('\n');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function formatBatchIngest(data: unknown, label: string): string {
|
|
280
|
+
const record = isRecord(data) ? data : {};
|
|
281
|
+
const imported = readNumber(record, 'imported');
|
|
282
|
+
const failed = readNumber(record, 'failed');
|
|
283
|
+
const sources = readArray(record, 'sources');
|
|
284
|
+
const errors = readArray(record, 'errors');
|
|
285
|
+
return [
|
|
286
|
+
`Agent Knowledge ${label} accepted`,
|
|
287
|
+
` imported: ${imported ?? sources.length}`,
|
|
288
|
+
` failed: ${failed ?? errors.length}`,
|
|
289
|
+
` sources: ${sources.length}`,
|
|
290
|
+
...sources.slice(0, 5).map((source) => ` - ${sourceLine(source)}`),
|
|
291
|
+
...(errors.length > 0 ? [' errors:', ...errors.slice(0, 5).map((error) => ` - ${cleanInline(error)}`)] : []),
|
|
292
|
+
].join('\n');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function formatReindex(data: unknown): string {
|
|
296
|
+
const record = isRecord(data) ? data : {};
|
|
297
|
+
const status = isRecord(record.status) ? record.status : {};
|
|
298
|
+
return [
|
|
299
|
+
'Agent Knowledge reindex complete',
|
|
300
|
+
` sources: ${readNumber(status, 'sourceCount') ?? 'unknown'}`,
|
|
301
|
+
` nodes: ${readNumber(status, 'nodeCount') ?? 'unknown'}`,
|
|
302
|
+
` edges: ${readNumber(status, 'edgeCount') ?? 'unknown'}`,
|
|
303
|
+
` issues: ${readNumber(status, 'issueCount') ?? 'unknown'}`,
|
|
304
|
+
' route: /api/goodvibes-agent/knowledge/reindex',
|
|
305
|
+
].join('\n');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function formatFailure(failure: AgentKnowledgeFailureLike, json: boolean): string {
|
|
309
|
+
if (json) return JSON.stringify(failure, null, 2);
|
|
310
|
+
return [
|
|
311
|
+
`Agent Knowledge error: ${failure.kind}`,
|
|
312
|
+
` ${failure.error}`,
|
|
313
|
+
` runtime: ${failure.baseUrl}`,
|
|
314
|
+
` route: ${failure.route}`,
|
|
315
|
+
failure.kind === 'version_mismatch' && failure.daemonVersion && failure.expectedSdkVersion
|
|
316
|
+
? ` versions: runtime=${failure.daemonVersion} expected=${failure.expectedSdkVersion}`
|
|
317
|
+
: null,
|
|
318
|
+
failure.kind === 'version_mismatch'
|
|
319
|
+
? ' next: update/restart the external GoodVibes runtime so /status matches the Agent SDK pin.'
|
|
320
|
+
: null,
|
|
321
|
+
failure.kind === 'daemon_route_unavailable'
|
|
322
|
+
? ' next: update/restart the external GoodVibes runtime to the SDK version required by this Agent package.'
|
|
323
|
+
: null,
|
|
324
|
+
].filter((line): line is string => Boolean(line)).join('\n');
|
|
325
|
+
}
|
package/src/cli/help.ts
CHANGED
|
@@ -184,14 +184,23 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
184
184
|
'knowledge status',
|
|
185
185
|
'knowledge ask <question> [--limit <n>] [--mode concise|standard|detailed]',
|
|
186
186
|
'knowledge search <query> [--limit <n>]',
|
|
187
|
+
'knowledge list [--kind sources|nodes|issues] [--limit <n>]',
|
|
188
|
+
'knowledge get <id>',
|
|
189
|
+
'knowledge connectors',
|
|
187
190
|
'knowledge ingest-url <url> [--title <title>] [--tags a,b] --yes',
|
|
191
|
+
'knowledge import-urls <path> --yes',
|
|
192
|
+
'knowledge import-bookmarks <path> --yes',
|
|
193
|
+
'knowledge reindex --yes',
|
|
188
194
|
],
|
|
189
195
|
summary: 'Call isolated Agent Knowledge/Wiki routes under /api/goodvibes-agent/knowledge. No default wiki or non-Agent fallback.',
|
|
190
196
|
examples: [
|
|
191
197
|
'knowledge status',
|
|
192
198
|
'knowledge ask "What is GoodVibes Agent?"',
|
|
193
199
|
'knowledge search "release checklist"',
|
|
200
|
+
'knowledge list --kind sources',
|
|
201
|
+
'knowledge connectors',
|
|
194
202
|
'knowledge ingest-url https://example.com/page --title "Reference" --yes',
|
|
203
|
+
'knowledge import-urls ~/agent-links.txt --yes',
|
|
195
204
|
],
|
|
196
205
|
},
|
|
197
206
|
ask: {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { CommandRegistry } from '../input/command-registry.ts';
|
|
5
|
+
import { registerBuiltinCommands } from '../input/commands.ts';
|
|
4
6
|
|
|
5
7
|
export interface PackageCliBinVerification {
|
|
6
8
|
readonly command: 'goodvibes-agent';
|
|
@@ -108,11 +110,40 @@ const PACKAGE_FACING_REQUIRED_TEXT: readonly {
|
|
|
108
110
|
{ path: 'docs/runtime-connection.md', required: ['/api/goodvibes-agent/knowledge'] },
|
|
109
111
|
{ path: 'docs/release-and-publishing.md', required: ['/api/goodvibes-agent/knowledge'] },
|
|
110
112
|
];
|
|
113
|
+
const NON_COMMAND_ROUTE_ROOTS = new Set(['api', 'status']);
|
|
111
114
|
|
|
112
115
|
function readPackageJson(root: string): Record<string, unknown> {
|
|
113
116
|
return JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')) as Record<string, unknown>;
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
function buildRegisteredSlashCommandNames(): ReadonlySet<string> {
|
|
120
|
+
const registry = new CommandRegistry();
|
|
121
|
+
registerBuiltinCommands(registry);
|
|
122
|
+
const names = new Set<string>();
|
|
123
|
+
for (const command of registry.list()) {
|
|
124
|
+
names.add(command.name);
|
|
125
|
+
for (const alias of command.aliases ?? []) names.add(alias);
|
|
126
|
+
}
|
|
127
|
+
return names;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function verifyPackageFacingSlashCommands(path: string, content: string, registeredCommands: ReadonlySet<string>): readonly string[] {
|
|
131
|
+
const failures: string[] = [];
|
|
132
|
+
const commandPattern = /(^|[\s`([])\/([a-z][a-z0-9_-]*)(?=$|[\s`.,;:)\]])/g;
|
|
133
|
+
const lines = content.split(/\r?\n/);
|
|
134
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
135
|
+
const line = lines[lineIndex] ?? '';
|
|
136
|
+
commandPattern.lastIndex = 0;
|
|
137
|
+
for (let match = commandPattern.exec(line); match !== null; match = commandPattern.exec(line)) {
|
|
138
|
+
const root = match[2] ?? '';
|
|
139
|
+
if (NON_COMMAND_ROUTE_ROOTS.has(root)) continue;
|
|
140
|
+
if (registeredCommands.has(root)) continue;
|
|
141
|
+
failures.push(`package-facing text ${path}:${lineIndex + 1} references unknown Agent slash command: /${root}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return failures;
|
|
145
|
+
}
|
|
146
|
+
|
|
116
147
|
function hasExecutableBit(path: string): boolean {
|
|
117
148
|
return existsSync(path) && (statSync(path).mode & 0o111) !== 0;
|
|
118
149
|
}
|
|
@@ -146,6 +177,7 @@ function registryPackDryRun(root: string): { readonly files: readonly string[];
|
|
|
146
177
|
|
|
147
178
|
export function verifyPackageFacingText(root: string): { readonly checkedPaths: readonly string[]; readonly failures: readonly string[] } {
|
|
148
179
|
const failures: string[] = [];
|
|
180
|
+
const registeredCommands = buildRegisteredSlashCommandNames();
|
|
149
181
|
for (const path of PACKAGE_FACING_TEXT_PATHS) {
|
|
150
182
|
const absolutePath = join(root, path);
|
|
151
183
|
if (!existsSync(absolutePath)) {
|
|
@@ -153,6 +185,9 @@ export function verifyPackageFacingText(root: string): { readonly checkedPaths:
|
|
|
153
185
|
continue;
|
|
154
186
|
}
|
|
155
187
|
const content = readFileSync(absolutePath, 'utf-8');
|
|
188
|
+
if (path !== 'CHANGELOG.md') {
|
|
189
|
+
failures.push(...verifyPackageFacingSlashCommands(path, content, registeredCommands));
|
|
190
|
+
}
|
|
156
191
|
for (const forbidden of PACKAGE_FACING_FORBIDDEN_TEXT) {
|
|
157
192
|
if (content.includes(forbidden)) {
|
|
158
193
|
failures.push(`package-facing text ${path} contains forbidden default/TUI route or policy: ${forbidden}`);
|
|
@@ -40,7 +40,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
|
|
|
40
40
|
label: 'GoodVibes runtime',
|
|
41
41
|
status: 'ready',
|
|
42
42
|
detail: `Agent will connect to ${input.daemonBaseUrl}; runtime ownership stays outside this product.`,
|
|
43
|
-
command: '/
|
|
43
|
+
command: '/health',
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
id: 'provider-model',
|
|
@@ -238,6 +238,10 @@ function runBundleCommand(args: readonly string[], ctx: CommandContext, skillReg
|
|
|
238
238
|
|
|
239
239
|
export async function runAgentSkillsRuntimeCommand(args: readonly string[], ctx: CommandContext): Promise<void> {
|
|
240
240
|
const sub = (args[0] ?? 'list').toLowerCase();
|
|
241
|
+
if (sub === 'local' || sub === 'agent') {
|
|
242
|
+
await runAgentSkillsRuntimeCommand(args.slice(1), ctx);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
241
245
|
const skillRegistry = registryFromContext(ctx);
|
|
242
246
|
try {
|
|
243
247
|
if (sub === 'bundle' || sub === 'bundles') {
|
|
@@ -356,7 +360,7 @@ export async function runAgentSkillsRuntimeCommand(args: readonly string[], ctx:
|
|
|
356
360
|
export function registerAgentSkillsRuntimeCommands(registry: CommandRegistry): void {
|
|
357
361
|
registry.register({
|
|
358
362
|
name: 'agent-skills',
|
|
359
|
-
aliases: ['askills', 'local-skills'],
|
|
363
|
+
aliases: ['askills', 'local-skills', 'skills', 'skill'],
|
|
360
364
|
description: 'Manage local GoodVibes Agent skills',
|
|
361
365
|
usage: '[list|enabled|search <query>|show <id>|create --name <name> --description <summary> --procedure <steps>|update <id> [--name ...] [--description ...] [--procedure ...]|enable <id>|disable <id>|review <id>|stale <id> <reason...>|delete <id> --yes|bundle ...]',
|
|
362
366
|
handler: runAgentSkillsRuntimeCommand,
|
|
@@ -103,9 +103,9 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
103
103
|
` recent failures: ${settings.recentFailureCount}`,
|
|
104
104
|
` staged bundle: ${settings.hasStagedManagedBundle ? 'present' : 'none'}`,
|
|
105
105
|
...(issues.length > 0 ? issues.map((issue) => ` issue: ${issue}`) : [' no active settings-control issues detected']),
|
|
106
|
-
' next: /
|
|
107
|
-
' next: /
|
|
108
|
-
' next: /
|
|
106
|
+
' next: /settings',
|
|
107
|
+
' next: /config <key>',
|
|
108
|
+
' next: /health repair settings',
|
|
109
109
|
].join('\n'));
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
@@ -131,8 +131,8 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
131
131
|
` active connections: ${snapshot.activeConnections}`,
|
|
132
132
|
` degraded: ${snapshot.degradedConnections}`,
|
|
133
133
|
...(issues.length > 0 ? issues.map((issue) => ` issue: ${issue}`) : [' no active remote recovery issues detected']),
|
|
134
|
-
' next: /
|
|
135
|
-
' next:
|
|
134
|
+
' next: /delegate <build/fix/review task> for explicit TUI build work',
|
|
135
|
+
' next: use the external runtime host for remote worker repair',
|
|
136
136
|
].join('\n'));
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
@@ -217,14 +217,14 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
217
217
|
lines.push(' domain: settings');
|
|
218
218
|
lines.push(...(
|
|
219
219
|
settings.conflicts.length > 0
|
|
220
|
-
? [' /
|
|
220
|
+
? [' /settings', ' /config <key>', ' runtime-owned managed setting repair stays external']
|
|
221
221
|
: [' no active settings repair actions suggested']
|
|
222
222
|
));
|
|
223
223
|
lines.push(' verify: /health settings');
|
|
224
224
|
} else if (domain === 'auth') {
|
|
225
225
|
lines.push(' domain: auth');
|
|
226
226
|
lines.push(' /auth review');
|
|
227
|
-
lines.push(' /
|
|
227
|
+
lines.push(' /provider');
|
|
228
228
|
lines.push(' /subscription providers');
|
|
229
229
|
lines.push(' runtime auth users/bootstrap cleanup: use the runtime-owning GoodVibes TUI or host tooling');
|
|
230
230
|
lines.push(' verify: /health auth');
|
|
@@ -237,15 +237,13 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
237
237
|
lines.push(' verify: /health accounts');
|
|
238
238
|
} else if (domain === 'services') {
|
|
239
239
|
lines.push(' domain: services');
|
|
240
|
-
lines.push(' /services doctor');
|
|
241
|
-
lines.push(' /services auth-review');
|
|
242
240
|
lines.push(' /health services');
|
|
241
|
+
lines.push(' runtime service repair belongs to the external GoodVibes runtime host');
|
|
243
242
|
lines.push(' verify: /health services');
|
|
244
243
|
} else if (domain === 'remote') {
|
|
245
244
|
lines.push(' domain: remote');
|
|
246
|
-
lines.push(' /
|
|
247
|
-
lines.push('
|
|
248
|
-
lines.push(' /remote setup');
|
|
245
|
+
lines.push(' /delegate <build/fix/review task> for explicit TUI build work');
|
|
246
|
+
lines.push(' remote runner setup and recovery belong to the external runtime host');
|
|
249
247
|
lines.push(' verify: /health remote');
|
|
250
248
|
} else if (domain === 'mcp') {
|
|
251
249
|
lines.push(' domain: mcp');
|
|
@@ -262,9 +260,8 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
262
260
|
} else if (domain === 'maintenance') {
|
|
263
261
|
lines.push(' domain: maintenance');
|
|
264
262
|
lines.push(' /health maintenance');
|
|
265
|
-
lines.push(' /
|
|
263
|
+
lines.push(' /mode');
|
|
266
264
|
lines.push(' /compact');
|
|
267
|
-
lines.push(' /panel tokens');
|
|
268
265
|
lines.push(' verify: /health maintenance');
|
|
269
266
|
} else {
|
|
270
267
|
lines.push(' domains: settings, auth, accounts, services, remote, mcp, continuity, maintenance');
|
|
@@ -329,7 +326,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
329
326
|
' /health remote',
|
|
330
327
|
' /health maintenance',
|
|
331
328
|
' /health repair <domain>',
|
|
332
|
-
' /
|
|
329
|
+
' /onboarding',
|
|
333
330
|
].join('\n'));
|
|
334
331
|
},
|
|
335
332
|
});
|
|
@@ -202,7 +202,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
202
202
|
...needingAttention.map((server) => (
|
|
203
203
|
` ${server.name} connected=${server.connected ? 'yes' : 'no'} freshness=${server.schemaFreshness} trust=${server.trustMode}`
|
|
204
204
|
)),
|
|
205
|
-
' next: /
|
|
205
|
+
' next: /auth review',
|
|
206
206
|
' next: /mcp repair <server>',
|
|
207
207
|
].join('\n')
|
|
208
208
|
: 'MCP Auth Review\n No MCP servers currently need auth or quarantine recovery.');
|
|
@@ -223,7 +223,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
223
223
|
selected.schemaFreshness === 'quarantined'
|
|
224
224
|
? `/mcp quarantine ${selected.name} approve operator --yes`
|
|
225
225
|
: null,
|
|
226
|
-
!selected.connected ? '/
|
|
226
|
+
!selected.connected ? '/auth review' : null,
|
|
227
227
|
'/mcp review',
|
|
228
228
|
'/health review',
|
|
229
229
|
].filter((entry): entry is string => entry !== null);
|
|
@@ -264,7 +264,7 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
|
|
|
264
264
|
ctx.print(` Reopened panels: ${reopenedPanels.join(', ')}`);
|
|
265
265
|
}
|
|
266
266
|
if ((meta.returnContext.remoteRunners?.length ?? 0) > 0) {
|
|
267
|
-
ctx.print(
|
|
267
|
+
ctx.print(' Remote re-entry: use the external runtime host for remote runner recovery; delegate explicit build/fix/review recovery from Agent.');
|
|
268
268
|
}
|
|
269
269
|
if ((meta.returnContext.worktreePaths?.length ?? 0) > 0) {
|
|
270
270
|
ctx.print(' Worktree re-entry: open GoodVibes TUI in the target workspace; delegate explicit build/fix/review recovery from Agent.');
|
|
@@ -20,7 +20,7 @@ const APPROVAL_ROWS = [
|
|
|
20
20
|
['network', 'why prompted: external hosts, fetch scope, egress policy', 'review via /approval review network'],
|
|
21
21
|
['delegate', 'why prompted: recursive agents, spawn ceilings, write-set inheritance', 'review via /approval review delegate'],
|
|
22
22
|
['mcp', 'why prompted: trust escalation, host scope, path scope, coherence mismatch', 'review via /mcp trust and /security'],
|
|
23
|
-
['remote', 'why prompted: runner trust, remote write scope, artifact requirements', 'review
|
|
23
|
+
['remote', 'why prompted: runner trust, remote write scope, artifact requirements', 'review delegated TUI execution context'],
|
|
24
24
|
['hook', 'why prompted: deny/mutate authority, blocking behavior, runner provenance', 'review via /hooks and /security'],
|
|
25
25
|
['plugin', 'why prompted: install/update lifecycle, provenance, capability grants', 'review via /marketplace and /security'],
|
|
26
26
|
] as const;
|
|
@@ -88,7 +88,7 @@ export class AutomationControlPanel extends ScrollableListPanel<AutomationRun> {
|
|
|
88
88
|
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
89
89
|
return [
|
|
90
90
|
{ command: '/schedule list', summary: 'inspect jobs and run history without mutating schedules' },
|
|
91
|
-
{ command: '/
|
|
91
|
+
{ command: '/schedule receipts', summary: 'review Agent routine promotion receipts' },
|
|
92
92
|
];
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -217,7 +217,7 @@ export async function buildProviderAccountSnapshot(
|
|
|
217
217
|
}
|
|
218
218
|
if (hasServiceOAuth && !serviceOauth?.usable) {
|
|
219
219
|
issues.push('Service OAuth is configured but missing a usable credential.');
|
|
220
|
-
recommendedActions.push(`Repair service OAuth credentials for ${providerId} in /
|
|
220
|
+
recommendedActions.push(`Repair service OAuth credentials for ${providerId} in /settings or the external runtime host.`);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
return {
|
|
@@ -55,8 +55,8 @@ export function buildProviderHealthDomainSummaries(
|
|
|
55
55
|
auth.bootstrapCredentialPresent ? 'Runtime bootstrap cleanup must be done from the runtime-owning TUI or host tooling.' : '',
|
|
56
56
|
].filter(Boolean),
|
|
57
57
|
nextSteps: auth.bootstrapCredentialPresent
|
|
58
|
-
? ['/auth review', '/
|
|
59
|
-
: ['/auth review', '/
|
|
58
|
+
? ['/auth review', '/provider', '/subscription providers']
|
|
59
|
+
: ['/auth review', '/provider'],
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
const settingIssueCount = settings.conflictCount + settings.recentFailureCount + (settings.hasStagedManagedBundle ? 1 : 0);
|
|
@@ -68,7 +68,7 @@ export function buildProviderHealthDomainSummaries(
|
|
|
68
68
|
: settingIssueCount > 0
|
|
69
69
|
? `${settings.conflictCount} conflicts / ${settings.recentFailureCount} failures${settings.hasStagedManagedBundle ? ' / staged bundle' : ''}`
|
|
70
70
|
: 'settings runtime API clean',
|
|
71
|
-
next: settingIssueCount > 0 ? '/
|
|
71
|
+
next: settingIssueCount > 0 ? '/settings' : '/config <key>',
|
|
72
72
|
details: [
|
|
73
73
|
settings.conflictCount > 0 ? `${settings.conflictCount} unresolved import conflict(s)` : '',
|
|
74
74
|
settings.recentFailureCount > 0 ? `${settings.recentFailureCount} recent sync or managed failure(s)` : '',
|
|
@@ -76,8 +76,8 @@ export function buildProviderHealthDomainSummaries(
|
|
|
76
76
|
settings.managedLockCount > 0 ? `${settings.managedLockCount} managed lock(s) enforced` : '',
|
|
77
77
|
].filter(Boolean),
|
|
78
78
|
nextSteps: settingIssueCount > 0
|
|
79
|
-
? ['/
|
|
80
|
-
: ['/
|
|
79
|
+
? ['/settings', '/config <key>', '/health settings']
|
|
80
|
+
: ['/config <key>'],
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
summaries.push({
|
|
@@ -86,7 +86,7 @@ export function buildProviderHealthDomainSummaries(
|
|
|
86
86
|
summary: remote.supervisor.sessions.length === 0
|
|
87
87
|
? 'no remote sessions tracked'
|
|
88
88
|
: `${remote.supervisor.sessions.length} sessions / ${remote.supervisor.degradedConnections} degraded`,
|
|
89
|
-
next: remote.supervisor.degradedConnections > 0 ? '/
|
|
89
|
+
next: remote.supervisor.degradedConnections > 0 ? '/delegate <build/fix/review task>' : '/health remote',
|
|
90
90
|
details: remote.supervisor.sessions.length === 0
|
|
91
91
|
? ['no remote sessions have been attached yet']
|
|
92
92
|
: remote.supervisor.sessions
|
|
@@ -99,8 +99,8 @@ export function buildProviderHealthDomainSummaries(
|
|
|
99
99
|
.slice(0, 3)
|
|
100
100
|
.map((entry) => `${entry.runnerId}: transport=${entry.transportState} heartbeat=${entry.heartbeat.status}${entry.lastError ? ` error=${entry.lastError}` : ''}`),
|
|
101
101
|
nextSteps: remote.supervisor.degradedConnections > 0
|
|
102
|
-
? ['/
|
|
103
|
-
: ['/remote
|
|
102
|
+
? ['/delegate <build/fix/review task>', '/health remote']
|
|
103
|
+
: ['/health remote'],
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
const degradedServers = security.mcpServers.filter((server) =>
|
|
@@ -141,7 +141,7 @@ export function buildProviderHealthDomainSummaries(
|
|
|
141
141
|
? 'warn'
|
|
142
142
|
: 'good',
|
|
143
143
|
summary: maintenance.summary,
|
|
144
|
-
next: maintenance.nextSteps[0] ?? '/
|
|
144
|
+
next: maintenance.nextSteps[0] ?? '/health maintenance',
|
|
145
145
|
details: maintenance.reasons.slice(0, 3),
|
|
146
146
|
nextSteps: maintenance.nextSteps,
|
|
147
147
|
});
|
|
@@ -266,7 +266,7 @@ export class SessionBrowserPanel extends BasePanel {
|
|
|
266
266
|
...formatReturnContextLines(selected.returnContext).map((line) =>
|
|
267
267
|
buildPanelLine(width, [[' ', DEFAULT_PANEL_PALETTE.dim], [truncateDisplay(line, Math.max(0, width - 2)), DEFAULT_PANEL_PALETTE.dim]])
|
|
268
268
|
),
|
|
269
|
-
buildPanelLine(width, [[' Next ', DEFAULT_PANEL_PALETTE.label], [
|
|
269
|
+
buildPanelLine(width, [[' Next ', DEFAULT_PANEL_PALETTE.label], ['/session resume', DEFAULT_PANEL_PALETTE.dim]]),
|
|
270
270
|
],
|
|
271
271
|
}
|
|
272
272
|
: { title: 'Selected', lines: [] };
|
|
@@ -76,13 +76,13 @@ export function evaluateSessionMaintenance(input: PanelSessionMaintenanceInput):
|
|
|
76
76
|
level = 'needs-repair';
|
|
77
77
|
summary = `Compact now to recover context headroom (${usagePct}% used).`;
|
|
78
78
|
reasons.push(`Context pressure is high at ${usagePct}% usage.`);
|
|
79
|
-
nextSteps.push('/compact', '/
|
|
79
|
+
nextSteps.push('/compact', '/context');
|
|
80
80
|
compactRecommended = true;
|
|
81
81
|
} else if (usagePct >= thresholdPct || remainingTokens <= 15_000) {
|
|
82
82
|
level = 'suggest-compact';
|
|
83
83
|
summary = `Watch context growth (${usagePct}% used).`;
|
|
84
84
|
reasons.push(`Context pressure is climbing at ${usagePct}% usage.`);
|
|
85
|
-
nextSteps.push('/
|
|
85
|
+
nextSteps.push('/context');
|
|
86
86
|
compactRecommended = true;
|
|
87
87
|
} else if (usagePct >= 70 || staleByMessageGrowth) {
|
|
88
88
|
level = 'watch';
|
|
@@ -92,7 +92,7 @@ export function evaluateSessionMaintenance(input: PanelSessionMaintenanceInput):
|
|
|
92
92
|
reasons.push(staleByMessageGrowth
|
|
93
93
|
? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
|
|
94
94
|
: `Context usage is climbing toward the ${thresholdPct}% maintenance threshold.`);
|
|
95
|
-
nextSteps.push('/
|
|
95
|
+
nextSteps.push('/context');
|
|
96
96
|
} else {
|
|
97
97
|
reasons.push('Context pressure is currently within the stable operating band.');
|
|
98
98
|
}
|
|
@@ -80,7 +80,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
|
|
|
80
80
|
return [
|
|
81
81
|
{ command: '/subscription login openai start --yes', summary: 'start the first-class OpenAI subscription flow' },
|
|
82
82
|
{ command: '/login provider <name> start --yes', summary: 'use the front-door auth flow for supported providers' },
|
|
83
|
-
{ command: '/
|
|
83
|
+
{ command: '/auth review', summary: 'inspect runtime auth posture without exposing token values' },
|
|
84
84
|
];
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -69,21 +69,23 @@ export function renderHelpOverlay(
|
|
|
69
69
|
// Each entry is [commandName, subcommandOrArgHint, description].
|
|
70
70
|
// Commands not registered in the live registry are omitted at render time.
|
|
71
71
|
const FEATURED_COMMANDS: Array<[name: string, argHint: string, desc: string]> = [
|
|
72
|
-
['
|
|
73
|
-
['
|
|
74
|
-
['
|
|
72
|
+
['agent', '', 'Open the Agent operator workspace'],
|
|
73
|
+
['onboarding', '', 'Open Agent setup with current settings preloaded'],
|
|
74
|
+
['knowledge', 'status', 'Inspect isolated Agent Knowledge readiness'],
|
|
75
|
+
['memory', '', 'Manage local Agent memory records'],
|
|
76
|
+
['personas', '', 'Manage serial Agent operating personas'],
|
|
77
|
+
['agent-skills', '', 'Manage local Agent skills and bundles'],
|
|
78
|
+
['routines', '', 'Manage reusable main-conversation routines'],
|
|
79
|
+
['workplan', '', 'Inspect shared work-plan state'],
|
|
80
|
+
['approval', '', 'Review and explicitly act on approvals'],
|
|
81
|
+
['schedule', '', 'Inspect schedules and routine promotion receipts'],
|
|
82
|
+
['delegate', '', 'Explicitly hand build/fix/review work to GoodVibes TUI'],
|
|
83
|
+
['mcp', '', 'Inspect MCP servers and tool readiness'],
|
|
75
84
|
['provider', '', 'Choose provider or model family'],
|
|
85
|
+
['model', '', 'Select the active model route'],
|
|
76
86
|
['subscription', '', 'Review provider logins and subscriptions'],
|
|
77
|
-
['
|
|
78
|
-
['
|
|
79
|
-
['security', '', 'Security review workspace'],
|
|
80
|
-
['policy', '', 'Simulation, lint, and preflight review'],
|
|
81
|
-
['incident', '', 'Incident workspace and export flows'],
|
|
82
|
-
['knowledge', '', 'Durable knowledge and review queue'],
|
|
83
|
-
['hooks', '', 'Hook workbench and runtime activity'],
|
|
84
|
-
['orchestration','', 'Graph and recursive-agent control room'],
|
|
85
|
-
['communication','', 'Structured agent communication workspace'],
|
|
86
|
-
['tasks', '', 'Read-only task view for list/show/pause/resume/output'],
|
|
87
|
+
['secrets', '', 'Manage secret references without printing values'],
|
|
88
|
+
['health', '', 'Run Agent runtime and setup diagnostics'],
|
|
87
89
|
];
|
|
88
90
|
|
|
89
91
|
// Build command rows from featured list, filtering out unregistered commands.
|
|
@@ -115,7 +117,29 @@ export function renderHelpOverlay(
|
|
|
115
117
|
|
|
116
118
|
if (commands && commands.length > 0) {
|
|
117
119
|
commandRows.push('', ' Available Slash Commands', ' ' + '\u2500'.repeat(40));
|
|
118
|
-
const preferred = [
|
|
120
|
+
const preferred = [
|
|
121
|
+
'agent',
|
|
122
|
+
'onboarding',
|
|
123
|
+
'knowledge',
|
|
124
|
+
'memory',
|
|
125
|
+
'personas',
|
|
126
|
+
'agent-skills',
|
|
127
|
+
'routines',
|
|
128
|
+
'workplan',
|
|
129
|
+
'approval',
|
|
130
|
+
'schedule',
|
|
131
|
+
'delegate',
|
|
132
|
+
'mcp',
|
|
133
|
+
'provider',
|
|
134
|
+
'model',
|
|
135
|
+
'subscription',
|
|
136
|
+
'secrets',
|
|
137
|
+
'health',
|
|
138
|
+
'settings',
|
|
139
|
+
'security',
|
|
140
|
+
'policy',
|
|
141
|
+
'tasks',
|
|
142
|
+
];
|
|
119
143
|
const seen = new Set<string>();
|
|
120
144
|
for (const name of preferred) {
|
|
121
145
|
const cmd = commands.find((entry) => entry.name === name);
|
|
@@ -148,7 +148,7 @@ function buildSettingContext(modal: SettingsModal, entry: SettingEntry): string[
|
|
|
148
148
|
];
|
|
149
149
|
|
|
150
150
|
if (entry.locked) lines.push(`Locked: ${entry.lockReason ?? 'This setting is locked by a higher-priority layer.'}`);
|
|
151
|
-
if (entry.conflict) lines.push(`Conflict:
|
|
151
|
+
if (entry.conflict) lines.push(`Conflict: inspect with /settings and resolve runtime-owned sync state in the external host.`);
|
|
152
152
|
|
|
153
153
|
lines.push('', entry.setting.description);
|
|
154
154
|
|
|
@@ -74,7 +74,7 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
|
|
|
74
74
|
options.conversation.log(`Resume: Reopened panels: ${reopenedPanels.join(', ')}`, { fg: '244' });
|
|
75
75
|
}
|
|
76
76
|
if ((meta.returnContext.remoteRunners?.length ?? 0) > 0) {
|
|
77
|
-
options.conversation.log(
|
|
77
|
+
options.conversation.log('Resume: Remote re-entry belongs to the external runtime host; delegate explicit build/fix/review recovery from Agent.', { fg: '244' });
|
|
78
78
|
}
|
|
79
79
|
if (returnContextMode === 'assisted') {
|
|
80
80
|
const helperModel = new HelperModel({
|
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.91';
|
|
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 {
|