@pellux/goodvibes-agent 0.1.107 → 0.1.108
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 +38 -13
- package/README.md +27 -11
- package/docs/README.md +5 -5
- package/docs/{runtime-connection.md → connected-services.md} +8 -8
- package/docs/getting-started.md +28 -12
- package/docs/release-and-publishing.md +4 -4
- package/package.json +2 -12
- package/src/agent/reminder-schedule-format.ts +75 -0
- package/src/agent/reminder-schedule.ts +494 -0
- package/src/agent/routine-schedule-format.ts +7 -7
- package/src/agent/routine-schedule-promotion.ts +1 -1
- package/src/agent/routine-schedule-receipts.ts +1 -1
- package/src/agent/skill-discovery.ts +2 -0
- package/src/cli/agent-knowledge-args.ts +93 -0
- package/src/cli/agent-knowledge-command.ts +200 -369
- package/src/cli/agent-knowledge-format.ts +58 -7
- package/src/cli/agent-knowledge-methods.ts +84 -0
- package/src/cli/agent-knowledge-runtime.ts +240 -0
- package/src/cli/completion.ts +0 -2
- package/src/cli/config-overrides.ts +2 -2
- package/src/cli/help.ts +34 -15
- package/src/cli/management-commands.ts +2 -2
- package/src/cli/management.ts +8 -8
- package/src/cli/package-verification.ts +7 -3
- package/src/cli/parser.ts +10 -4
- package/src/cli/service-posture.ts +6 -6
- package/src/cli/status.ts +32 -32
- package/src/input/agent-workspace-activation.ts +24 -13
- package/src/input/agent-workspace-basic-command-editors.ts +448 -0
- package/src/input/agent-workspace-categories.ts +42 -34
- package/src/input/agent-workspace-channels.ts +3 -3
- package/src/input/agent-workspace-command-editor.ts +65 -0
- package/src/input/agent-workspace-editors.ts +17 -2
- package/src/input/agent-workspace-knowledge-query-editor.ts +74 -0
- package/src/input/agent-workspace-knowledge-url-editor.ts +95 -0
- package/src/input/agent-workspace-reminder-schedule-editor.ts +125 -0
- package/src/input/agent-workspace-routine-schedule-editor.ts +127 -0
- package/src/input/agent-workspace-setup.ts +2 -2
- package/src/input/agent-workspace-types.ts +20 -2
- package/src/input/agent-workspace-voice-media.ts +5 -5
- package/src/input/agent-workspace.ts +39 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
- package/src/input/commands/agent-skills-runtime.ts +94 -2
- package/src/input/commands/brief-runtime.ts +126 -0
- package/src/input/commands/channels-runtime.ts +47 -0
- package/src/input/commands/health-runtime.ts +10 -10
- package/src/input/commands/knowledge.ts +176 -1
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +10 -10
- package/src/input/commands/policy-dispatch.ts +1 -1
- package/src/input/commands/schedule-runtime.ts +42 -5
- package/src/input/commands/security-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/commands/shell-core.ts +4 -2
- package/src/input/commands/tasks-runtime.ts +3 -3
- package/src/input/commands.ts +4 -0
- package/src/input/handler-onboarding.ts +4 -4
- package/src/input/handler.ts +3 -2
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +13 -13
- package/src/input/onboarding/onboarding-wizard-steps.ts +8 -8
- package/src/input/settings-modal-agent-policy.ts +1 -1
- package/src/input/slash-command-parser.ts +60 -0
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +2 -2
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-health-domains.ts +6 -6
- package/src/panels/subscription-panel.ts +1 -1
- package/src/panels/tasks-panel.ts +3 -3
- package/src/renderer/agent-workspace.ts +43 -30
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/settings-modal.ts +13 -13
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap.ts +8 -8
- package/src/runtime/index.ts +2 -2
- package/src/runtime/onboarding/derivation.ts +6 -6
- package/src/shell/service-settings-sync.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -221,6 +221,51 @@ export function formatConnectors(data: unknown): string {
|
|
|
221
221
|
].join('\n');
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
export function formatConnector(data: unknown, id: string): string {
|
|
225
|
+
const record = isRecord(data) ? data : {};
|
|
226
|
+
const connector = isRecord(record.connector) ? record.connector : record;
|
|
227
|
+
const connectorId = cleanInline(connector.id) || id;
|
|
228
|
+
const name = cleanInline(connector.displayName) || connectorId;
|
|
229
|
+
const description = cleanInline(connector.description);
|
|
230
|
+
const sourceType = cleanInline(connector.sourceType);
|
|
231
|
+
const capabilities = readArray(connector, 'capabilities').map(cleanInline).filter(Boolean);
|
|
232
|
+
const examples = readArray(connector, 'examples');
|
|
233
|
+
return [
|
|
234
|
+
`Agent Knowledge connector: ${connectorId}`,
|
|
235
|
+
` name: ${name}`,
|
|
236
|
+
sourceType ? ` sourceType: ${sourceType}` : null,
|
|
237
|
+
description ? ` description: ${description}` : null,
|
|
238
|
+
capabilities.length > 0 ? ` capabilities: ${capabilities.join(', ')}` : null,
|
|
239
|
+
` examples: ${examples.length}`,
|
|
240
|
+
' route: /api/goodvibes-agent/knowledge/connectors/{id}',
|
|
241
|
+
].filter((line): line is string => Boolean(line)).join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function formatConnectorDoctor(data: unknown, id: string): string {
|
|
245
|
+
const record = isRecord(data) ? data : {};
|
|
246
|
+
const ready = readBoolean(record, 'ready');
|
|
247
|
+
const summary = cleanInline(record.summary);
|
|
248
|
+
const checks = readArray(record, 'checks');
|
|
249
|
+
const hints = readArray(record, 'hints').map(cleanInline).filter(Boolean);
|
|
250
|
+
return [
|
|
251
|
+
`Agent Knowledge connector doctor: ${cleanInline(record.connectorId) || id}`,
|
|
252
|
+
` ready: ${ready === null ? 'unknown' : yesNo(ready)}`,
|
|
253
|
+
summary ? ` summary: ${summary}` : null,
|
|
254
|
+
checks.length > 0 ? ' checks:' : null,
|
|
255
|
+
...checks.slice(0, 10).map((check) => {
|
|
256
|
+
const item = isRecord(check) ? check : {};
|
|
257
|
+
const checkId = cleanInline(item.id) || 'check';
|
|
258
|
+
const status = cleanInline(item.status) || 'unknown';
|
|
259
|
+
const label = cleanInline(item.label);
|
|
260
|
+
const detail = cleanInline(item.detail);
|
|
261
|
+
return ` - ${checkId} [${status}] ${label}${detail ? ` - ${detail}` : ''}`;
|
|
262
|
+
}),
|
|
263
|
+
hints.length > 0 ? ' hints:' : null,
|
|
264
|
+
...hints.slice(0, 8).map((hint) => ` - ${hint}`),
|
|
265
|
+
' route: /api/goodvibes-agent/knowledge/connectors/{id}/doctor',
|
|
266
|
+
].filter((line): line is string => Boolean(line)).join('\n');
|
|
267
|
+
}
|
|
268
|
+
|
|
224
269
|
export function formatAsk(data: unknown, query: string): string {
|
|
225
270
|
const record = isRecord(data) ? data : {};
|
|
226
271
|
const answer = isRecord(record.answer) ? record.answer : record;
|
|
@@ -261,18 +306,24 @@ export function formatSearch(data: unknown, query: string): string {
|
|
|
261
306
|
].join('\n');
|
|
262
307
|
}
|
|
263
308
|
|
|
264
|
-
export function formatIngest(
|
|
309
|
+
export function formatIngest(
|
|
310
|
+
data: unknown,
|
|
311
|
+
target: string,
|
|
312
|
+
label = 'ingest-url',
|
|
313
|
+
route = '/api/goodvibes-agent/knowledge/ingest/url',
|
|
314
|
+
targetLabel = 'url',
|
|
315
|
+
): string {
|
|
265
316
|
const record = isRecord(data) ? data : {};
|
|
266
317
|
const source = isRecord(record.source) ? record.source : record;
|
|
267
318
|
const sourceId = cleanInline(source.id);
|
|
268
|
-
const canonicalUri = cleanInline(source.canonicalUri) || cleanInline(source.sourceUri) ||
|
|
319
|
+
const canonicalUri = cleanInline(source.canonicalUri) || cleanInline(source.sourceUri) || target;
|
|
269
320
|
const artifactId = cleanInline(record.artifactId);
|
|
270
321
|
return [
|
|
271
|
-
|
|
322
|
+
`Agent Knowledge ${label} accepted`,
|
|
272
323
|
` source: ${sourceId || '(pending)'}`,
|
|
273
|
-
`
|
|
324
|
+
` ${targetLabel}: ${canonicalUri}`,
|
|
274
325
|
artifactId ? ` artifact: ${artifactId}` : null,
|
|
275
|
-
|
|
326
|
+
` route: ${route}`,
|
|
276
327
|
].filter((line): line is string => Boolean(line)).join('\n');
|
|
277
328
|
}
|
|
278
329
|
|
|
@@ -316,10 +367,10 @@ export function formatFailure(failure: AgentKnowledgeFailureLike, json: boolean)
|
|
|
316
367
|
? ` versions: runtime=${failure.daemonVersion} expected=${failure.expectedSdkVersion}`
|
|
317
368
|
: null,
|
|
318
369
|
failure.kind === 'version_mismatch'
|
|
319
|
-
? ' next: update
|
|
370
|
+
? ' next: update connected GoodVibes services so /status matches the Agent SDK pin.'
|
|
320
371
|
: null,
|
|
321
372
|
failure.kind === 'daemon_route_unavailable'
|
|
322
|
-
? ' next: update
|
|
373
|
+
? ' next: update connected GoodVibes services to the SDK version required by this Agent package.'
|
|
323
374
|
: null,
|
|
324
375
|
].filter((line): line is string => Boolean(line)).join('\n');
|
|
325
376
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export interface DaemonCallMethod {
|
|
2
|
+
readonly kind: string;
|
|
3
|
+
readonly route: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const AGENT_KNOWLEDGE_METHODS = {
|
|
7
|
+
status: {
|
|
8
|
+
kind: 'agentKnowledge.status',
|
|
9
|
+
route: '/api/goodvibes-agent/knowledge/status',
|
|
10
|
+
},
|
|
11
|
+
ask: {
|
|
12
|
+
kind: 'agentKnowledge.ask',
|
|
13
|
+
route: '/api/goodvibes-agent/knowledge/ask',
|
|
14
|
+
},
|
|
15
|
+
search: {
|
|
16
|
+
kind: 'agentKnowledge.search',
|
|
17
|
+
route: '/api/goodvibes-agent/knowledge/search',
|
|
18
|
+
},
|
|
19
|
+
sourcesList: {
|
|
20
|
+
kind: 'agentKnowledge.sources.list',
|
|
21
|
+
route: '/api/goodvibes-agent/knowledge/sources',
|
|
22
|
+
},
|
|
23
|
+
nodesList: {
|
|
24
|
+
kind: 'agentKnowledge.nodes.list',
|
|
25
|
+
route: '/api/goodvibes-agent/knowledge/nodes',
|
|
26
|
+
},
|
|
27
|
+
issuesList: {
|
|
28
|
+
kind: 'agentKnowledge.issues.list',
|
|
29
|
+
route: '/api/goodvibes-agent/knowledge/issues',
|
|
30
|
+
},
|
|
31
|
+
itemGet: {
|
|
32
|
+
kind: 'agentKnowledge.item.get',
|
|
33
|
+
route: '/api/goodvibes-agent/knowledge/items/{id}',
|
|
34
|
+
},
|
|
35
|
+
map: {
|
|
36
|
+
kind: 'agentKnowledge.map',
|
|
37
|
+
route: '/api/goodvibes-agent/knowledge/map',
|
|
38
|
+
},
|
|
39
|
+
connectorsList: {
|
|
40
|
+
kind: 'agentKnowledge.connectors.list',
|
|
41
|
+
route: '/api/goodvibes-agent/knowledge/connectors',
|
|
42
|
+
},
|
|
43
|
+
connectorGet: {
|
|
44
|
+
kind: 'agentKnowledge.connector.get',
|
|
45
|
+
route: '/api/goodvibes-agent/knowledge/connectors/{id}',
|
|
46
|
+
},
|
|
47
|
+
connectorDoctor: {
|
|
48
|
+
kind: 'agentKnowledge.connector.doctor',
|
|
49
|
+
route: '/api/goodvibes-agent/knowledge/connectors/{id}/doctor',
|
|
50
|
+
},
|
|
51
|
+
ingestUrl: {
|
|
52
|
+
kind: 'agentKnowledge.ingest.url',
|
|
53
|
+
route: '/api/goodvibes-agent/knowledge/ingest/url',
|
|
54
|
+
},
|
|
55
|
+
ingestArtifact: {
|
|
56
|
+
kind: 'agentKnowledge.ingest.artifact',
|
|
57
|
+
route: '/api/goodvibes-agent/knowledge/ingest/artifact',
|
|
58
|
+
},
|
|
59
|
+
ingestUrls: {
|
|
60
|
+
kind: 'agentKnowledge.ingest.urls',
|
|
61
|
+
route: '/api/goodvibes-agent/knowledge/ingest/urls',
|
|
62
|
+
},
|
|
63
|
+
ingestBookmarks: {
|
|
64
|
+
kind: 'agentKnowledge.ingest.bookmarks',
|
|
65
|
+
route: '/api/goodvibes-agent/knowledge/ingest/bookmarks',
|
|
66
|
+
},
|
|
67
|
+
ingestBrowserHistory: {
|
|
68
|
+
kind: 'agentKnowledge.ingest.browserHistory',
|
|
69
|
+
route: '/api/goodvibes-agent/knowledge/ingest/browser-history',
|
|
70
|
+
},
|
|
71
|
+
ingestConnector: {
|
|
72
|
+
kind: 'agentKnowledge.ingest.connector',
|
|
73
|
+
route: '/api/goodvibes-agent/knowledge/ingest/connector',
|
|
74
|
+
},
|
|
75
|
+
reindex: {
|
|
76
|
+
kind: 'agentKnowledge.reindex',
|
|
77
|
+
route: '/api/goodvibes-agent/knowledge/reindex',
|
|
78
|
+
},
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
81
|
+
export const DELEGATION_METHOD = {
|
|
82
|
+
kind: 'sessions.messages.create',
|
|
83
|
+
route: 'sessions.messages.create',
|
|
84
|
+
} as const;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createBrowserAgentSdk } from '@pellux/goodvibes-sdk/browser/agent';
|
|
4
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
|
+
import { SDK_VERSION, VERSION } from '../version.ts';
|
|
6
|
+
import type { CliCommandRuntime } from './management.ts';
|
|
7
|
+
import type { DaemonCallMethod } from './agent-knowledge-methods.ts';
|
|
8
|
+
|
|
9
|
+
export type JsonRecord = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
export interface AgentDaemonConnection {
|
|
12
|
+
readonly baseUrl: string;
|
|
13
|
+
readonly token: string | null;
|
|
14
|
+
readonly tokenPath: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AgentKnowledgeFailure {
|
|
18
|
+
readonly ok: false;
|
|
19
|
+
readonly kind: 'daemon_unavailable' | 'auth_required' | 'version_mismatch' | 'daemon_route_unavailable' | 'daemon_error';
|
|
20
|
+
readonly error: string;
|
|
21
|
+
readonly baseUrl: string;
|
|
22
|
+
readonly route: string;
|
|
23
|
+
readonly daemonVersion?: string;
|
|
24
|
+
readonly expectedSdkVersion?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AgentKnowledgeSuccess<TData> {
|
|
28
|
+
readonly ok: true;
|
|
29
|
+
readonly kind: string;
|
|
30
|
+
readonly route: string;
|
|
31
|
+
readonly data: TData;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type AgentKnowledgeResult<TData> = AgentKnowledgeSuccess<TData> | AgentKnowledgeFailure;
|
|
35
|
+
|
|
36
|
+
export function isRecord(value: unknown): value is JsonRecord {
|
|
37
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readString(record: JsonRecord | null, key: string): string | null {
|
|
41
|
+
const value = record?.[key];
|
|
42
|
+
return typeof value === 'string' ? value : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function readPackageMetadata(): { readonly version: string; readonly sdkVersion: string } {
|
|
46
|
+
return { version: VERSION, sdkVersion: SDK_VERSION };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function resolveDaemonConnection(runtime: CliCommandRuntime): AgentDaemonConnection {
|
|
50
|
+
const host = String(runtime.configManager.get('controlPlane.host') ?? '127.0.0.1');
|
|
51
|
+
const port = Number(runtime.configManager.get('controlPlane.port') ?? 3421);
|
|
52
|
+
const baseUrl = `http://${host}:${Number.isFinite(port) ? port : 3421}`;
|
|
53
|
+
const tokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
|
|
54
|
+
if (!existsSync(tokenPath)) return { baseUrl, token: null, tokenPath };
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(readFileSync(tokenPath, 'utf-8')) as unknown;
|
|
57
|
+
const token = isRecord(parsed) && typeof parsed.token === 'string' ? parsed.token : null;
|
|
58
|
+
return { baseUrl, token, tokenPath };
|
|
59
|
+
} catch {
|
|
60
|
+
return { baseUrl, token: null, tokenPath };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{ readonly ok: boolean; readonly status: number; readonly body: unknown }> {
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(`${connection.baseUrl}/status`, {
|
|
67
|
+
headers: connection.token ? { authorization: `Bearer ${connection.token}` } : undefined,
|
|
68
|
+
});
|
|
69
|
+
const text = await response.text();
|
|
70
|
+
let body: unknown = text;
|
|
71
|
+
try {
|
|
72
|
+
body = JSON.parse(text) as unknown;
|
|
73
|
+
} catch {
|
|
74
|
+
body = text;
|
|
75
|
+
}
|
|
76
|
+
return { ok: response.ok, status: response.status, body };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return { ok: false, status: 0, body: summarizeError(error) };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function classifyKnowledgeError(error: unknown, connection: AgentDaemonConnection, route: string): Promise<AgentKnowledgeFailure> {
|
|
83
|
+
const message = summarizeError(error);
|
|
84
|
+
const lower = message.toLowerCase();
|
|
85
|
+
if (lower.includes('401') || lower.includes('unauthorized') || lower.includes('auth')) {
|
|
86
|
+
return { ok: false, kind: 'auth_required', error: message, baseUrl: connection.baseUrl, route };
|
|
87
|
+
}
|
|
88
|
+
if (lower.includes('404') || lower.includes('not found')) {
|
|
89
|
+
const metadata = readPackageMetadata();
|
|
90
|
+
const daemon = await fetchDaemonStatus(connection);
|
|
91
|
+
const daemonRecord = isRecord(daemon.body) ? daemon.body : {};
|
|
92
|
+
const daemonVersion = readString(daemonRecord, 'version') ?? 'unknown';
|
|
93
|
+
if (daemon.ok && daemonVersion !== metadata.sdkVersion) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
kind: 'version_mismatch',
|
|
97
|
+
error: `Connected GoodVibes service SDK version ${daemonVersion} does not match Agent SDK pin ${metadata.sdkVersion}; Agent Knowledge route is unavailable.`,
|
|
98
|
+
baseUrl: connection.baseUrl,
|
|
99
|
+
route,
|
|
100
|
+
daemonVersion,
|
|
101
|
+
expectedSdkVersion: metadata.sdkVersion,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return { ok: false, kind: 'daemon_route_unavailable', error: message, baseUrl: connection.baseUrl, route };
|
|
105
|
+
}
|
|
106
|
+
if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
|
|
107
|
+
return { ok: false, kind: 'daemon_unavailable', error: message, baseUrl: connection.baseUrl, route };
|
|
108
|
+
}
|
|
109
|
+
return { ok: false, kind: 'daemon_error', error: message, baseUrl: connection.baseUrl, route };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createAgentSdk(connection: AgentDaemonConnection) {
|
|
113
|
+
return createBrowserAgentSdk({
|
|
114
|
+
baseUrl: connection.baseUrl,
|
|
115
|
+
authToken: connection.token,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function postAgentKnowledgeJson<TData>(
|
|
120
|
+
connection: AgentDaemonConnection,
|
|
121
|
+
route: string,
|
|
122
|
+
body: JsonRecord,
|
|
123
|
+
): Promise<TData> {
|
|
124
|
+
const response = await fetch(`${connection.baseUrl}${route}`, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: {
|
|
127
|
+
authorization: `Bearer ${connection.token ?? ''}`,
|
|
128
|
+
'content-type': 'application/json',
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify(body),
|
|
131
|
+
});
|
|
132
|
+
const text = await response.text();
|
|
133
|
+
let parsed: unknown = text;
|
|
134
|
+
if (text.trim()) {
|
|
135
|
+
try {
|
|
136
|
+
parsed = JSON.parse(text) as unknown;
|
|
137
|
+
} catch {
|
|
138
|
+
parsed = text;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
const detail = isRecord(parsed) && typeof parsed.error === 'string' ? parsed.error : text;
|
|
143
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}${detail ? `: ${detail}` : ''}`);
|
|
144
|
+
}
|
|
145
|
+
return parsed as TData;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function queryRoute(route: string, query: JsonRecord): string {
|
|
149
|
+
const params = new URLSearchParams();
|
|
150
|
+
for (const [key, value] of Object.entries(query)) {
|
|
151
|
+
if (value === undefined || value === null || value === '') continue;
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
for (const item of value) {
|
|
154
|
+
if (typeof item === 'string' && item.trim().length > 0) params.append(key, item);
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
159
|
+
params.set(key, String(value));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const suffix = params.toString();
|
|
163
|
+
return suffix ? `${route}?${suffix}` : route;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function getAgentKnowledgeJson<TData>(
|
|
167
|
+
connection: AgentDaemonConnection,
|
|
168
|
+
route: string,
|
|
169
|
+
query: JsonRecord = {},
|
|
170
|
+
): Promise<TData> {
|
|
171
|
+
const response = await fetch(`${connection.baseUrl}${queryRoute(route, query)}`, {
|
|
172
|
+
headers: {
|
|
173
|
+
authorization: `Bearer ${connection.token ?? ''}`,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
const text = await response.text();
|
|
177
|
+
let parsed: unknown = text;
|
|
178
|
+
if (text.trim()) {
|
|
179
|
+
try {
|
|
180
|
+
parsed = JSON.parse(text) as unknown;
|
|
181
|
+
} catch {
|
|
182
|
+
parsed = text;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
const detail = isRecord(parsed) && typeof parsed.error === 'string' ? parsed.error : text;
|
|
187
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}${detail ? `: ${detail}` : ''}`);
|
|
188
|
+
}
|
|
189
|
+
return parsed as TData;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function findDisallowedKnowledgeScopeFlag(args: readonly string[]): string | null {
|
|
193
|
+
const disallowed = [
|
|
194
|
+
'--space',
|
|
195
|
+
'--knowledge-space',
|
|
196
|
+
'--knowledge-space-id',
|
|
197
|
+
['--knowledge', 'SpaceId'].join(''),
|
|
198
|
+
'--include-all-spaces',
|
|
199
|
+
['--include', 'AllSpaces'].join(''),
|
|
200
|
+
['--home', 'graph'].join(''),
|
|
201
|
+
['--home', '-graph'].join(''),
|
|
202
|
+
];
|
|
203
|
+
for (const token of args) {
|
|
204
|
+
for (const flag of disallowed) {
|
|
205
|
+
if (token === flag || token.startsWith(`${flag}=`)) return flag;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function formatScopeFlagRejection(flag: string): string {
|
|
212
|
+
return [
|
|
213
|
+
`Agent Knowledge is isolated; ${flag} is not accepted.`,
|
|
214
|
+
'GoodVibes Agent must not use default Knowledge/Wiki or non-Agent product spaces.',
|
|
215
|
+
'Use only /api/goodvibes-agent/knowledge/* Agent-owned routes.',
|
|
216
|
+
].join('\n');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function runKnowledgeCall<TData>(
|
|
220
|
+
runtime: CliCommandRuntime,
|
|
221
|
+
method: DaemonCallMethod,
|
|
222
|
+
call: (connection: AgentDaemonConnection) => Promise<TData>,
|
|
223
|
+
): Promise<AgentKnowledgeResult<TData>> {
|
|
224
|
+
const connection = resolveDaemonConnection(runtime);
|
|
225
|
+
if (!connection.token) {
|
|
226
|
+
return {
|
|
227
|
+
ok: false,
|
|
228
|
+
kind: 'auth_required',
|
|
229
|
+
error: `No runtime operator token found at ${connection.tokenPath}`,
|
|
230
|
+
baseUrl: connection.baseUrl,
|
|
231
|
+
route: method.route,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const data = await call(connection);
|
|
236
|
+
return { ok: true, kind: method.kind, route: method.route, data };
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return classifyKnowledgeError(error, connection, method.route);
|
|
239
|
+
}
|
|
240
|
+
}
|
package/src/cli/completion.ts
CHANGED
|
@@ -36,13 +36,13 @@ function parseRuntimeUrl(rawValue: string, source: string): { readonly host: str
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (url.protocol !== 'http:') {
|
|
39
|
-
throw new ConfigError(`${source} must use http:// because Agent connects to the local GoodVibes
|
|
39
|
+
throw new ConfigError(`${source} must use http:// because Agent connects to the local GoodVibes API.`);
|
|
40
40
|
}
|
|
41
41
|
if (!url.hostname) {
|
|
42
42
|
throw new ConfigError(`${source} must include a hostname.`);
|
|
43
43
|
}
|
|
44
44
|
if ((url.pathname && url.pathname !== '/') || url.search || url.hash) {
|
|
45
|
-
throw new ConfigError(`${source} must point at the
|
|
45
|
+
throw new ConfigError(`${source} must point at the connected GoodVibes API root, not a path, query, or hash.`);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const port = url.port ? Number.parseInt(url.port, 10) : 3421;
|
package/src/cli/help.ts
CHANGED
|
@@ -27,9 +27,23 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
|
|
|
27
27
|
return [
|
|
28
28
|
`Usage: ${binary} [OPTIONS] [PROMPT]`,
|
|
29
29
|
` ${binary} [OPTIONS] <COMMAND> [ARGS]`,
|
|
30
|
+
'',
|
|
31
|
+
'Primary use:',
|
|
32
|
+
` ${binary} Launch the interactive Agent TUI`,
|
|
33
|
+
` ${binary} setup Open first-run Agent setup`,
|
|
34
|
+
'',
|
|
35
|
+
'Inside the TUI:',
|
|
36
|
+
' /agent Open the operator workspace',
|
|
37
|
+
' /setup Open setup/config flows',
|
|
38
|
+
' /model Choose provider and model',
|
|
39
|
+
' /knowledge Use isolated Agent Knowledge',
|
|
40
|
+
' /personas, /skills Tune local Agent behavior',
|
|
41
|
+
' /routines Run local routines in the main conversation',
|
|
42
|
+
' /schedule remind Create confirmed reminders or inspect schedules',
|
|
43
|
+
' /delegate Explicitly hand build/fix/review work to GoodVibes TUI',
|
|
30
44
|
'',
|
|
31
45
|
'Commands:',
|
|
32
|
-
' tui
|
|
46
|
+
' tui [path] Start the interactive Agent terminal UI (default)',
|
|
33
47
|
' run|exec [prompt] Run non-interactively with text/json/stream-json output',
|
|
34
48
|
' status Print config, provider, auth, and setup posture',
|
|
35
49
|
' doctor Print status plus setup warnings',
|
|
@@ -40,9 +54,9 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
|
|
|
40
54
|
' personas Manage local Agent personas',
|
|
41
55
|
' skills Manage local Agent skills and skill bundles',
|
|
42
56
|
' memory Manage Agent-owned durable memory records',
|
|
43
|
-
' routines Inspect local routines and explicitly promote one to
|
|
44
|
-
' auth Inspect Agent auth posture and
|
|
45
|
-
' compat Inspect Agent SDK pin,
|
|
57
|
+
' routines Inspect local routines and explicitly promote one to a connected schedule',
|
|
58
|
+
' auth Inspect Agent auth posture and connection token state',
|
|
59
|
+
' compat Inspect Agent SDK pin, connected service version, and Agent Knowledge route readiness',
|
|
46
60
|
' knowledge Use isolated Agent Knowledge/Wiki routes',
|
|
47
61
|
' ask|search Shortcuts for isolated Agent Knowledge ask/search',
|
|
48
62
|
' delegate Explicitly delegate build/fix/review work to GoodVibes TUI',
|
|
@@ -60,7 +74,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
|
|
|
60
74
|
' -m, --model <registryKey> Override model. provider:model infers --provider',
|
|
61
75
|
' --provider <id> Override provider',
|
|
62
76
|
' --agent-profile <name> Use an isolated Agent profile home',
|
|
63
|
-
' --runtime-url <url>
|
|
77
|
+
' --runtime-url <url> Connected GoodVibes API root',
|
|
64
78
|
' -C, --cd <dir> Set working directory for this launch',
|
|
65
79
|
' --working-dir <dir> Alias for --cd',
|
|
66
80
|
' -c, --config <key=value> Override a config value for this launch',
|
|
@@ -81,7 +95,6 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
|
|
|
81
95
|
'',
|
|
82
96
|
'Examples:',
|
|
83
97
|
` ${binary}`,
|
|
84
|
-
` ${binary} launch`,
|
|
85
98
|
` ${binary} --no-alt-screen`,
|
|
86
99
|
` ${binary} --cd ~/work/project --model openai:gpt-5.2`,
|
|
87
100
|
` ${binary} setup`,
|
|
@@ -120,9 +133,9 @@ type CommandHelp = {
|
|
|
120
133
|
|
|
121
134
|
const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
122
135
|
tui: {
|
|
123
|
-
usage: ['tui [path]', '
|
|
136
|
+
usage: ['tui [path]', '[prompt]'],
|
|
124
137
|
summary: 'Start the interactive Agent terminal UI. A prompt starts Agent with that prompt seeded.',
|
|
125
|
-
examples: ['', '
|
|
138
|
+
examples: ['', 'tui ~/work/project', '"summarize current tasks"'],
|
|
126
139
|
},
|
|
127
140
|
run: {
|
|
128
141
|
usage: ['run [prompt] [--output text|json|stream-json]', 'exec [prompt]'],
|
|
@@ -136,7 +149,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
136
149
|
},
|
|
137
150
|
status: {
|
|
138
151
|
usage: ['status', 'status --json', '--runtime-url http://127.0.0.1:3421 status'],
|
|
139
|
-
summary: 'Print Agent config, provider, auth,
|
|
152
|
+
summary: 'Print Agent config, provider, auth, connected-service state, and setup posture.',
|
|
140
153
|
examples: ['status', 'status --json', '--runtime-url http://127.0.0.1:3421 status'],
|
|
141
154
|
},
|
|
142
155
|
doctor: {
|
|
@@ -151,7 +164,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
151
164
|
},
|
|
152
165
|
profiles: {
|
|
153
166
|
usage: ['profiles list', 'profiles templates', 'profiles templates export <id> <path> --yes', 'profiles templates import <path> --yes', 'profiles show <name>', 'profiles create <name> [--template <id>] --yes', 'profiles delete <name> --yes', '--agent-profile <name>'],
|
|
154
|
-
summary: 'Create and inspect isolated Agent profile homes, with starter templates for household, research, travel, operations, personal productivity, and local imported starters. A profile changes Agent-local config, sessions, memory, personas, skills, routines, and setup paths without changing
|
|
167
|
+
summary: 'Create and inspect isolated Agent profile homes, with starter templates for household, research, travel, operations, personal productivity, and local imported starters. A profile changes Agent-local config, sessions, memory, personas, skills, routines, and setup paths without changing connected GoodVibes services.',
|
|
155
168
|
examples: ['profiles templates', 'profiles templates export research ./research-starter.json --yes', 'profiles templates import ./research-starter.json --yes', 'profiles create household --template household --yes', '--agent-profile household status'],
|
|
156
169
|
},
|
|
157
170
|
personas: {
|
|
@@ -238,7 +251,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
238
251
|
'routines receipt <receipt-id>',
|
|
239
252
|
'routines promote <id> (--cron <expr>|--every <interval>|--at <iso-time>) [--timezone <tz>] [--name <schedule-name>] [--provider <id>] [--model <model>] [--delivery-channel <channel[:route[:label]]>|--delivery-route <route[:label]>|--delivery-webhook <url>|--delivery-link <url>] [--disabled] --yes',
|
|
240
253
|
],
|
|
241
|
-
summary: 'Inspect Agent-local routines, review local promotion receipts, reconcile receipts against live
|
|
254
|
+
summary: 'Inspect Agent-local routines, review local promotion receipts, reconcile receipts against live connected schedules, and explicitly promote a reviewed routine into a GoodVibes schedule. Without --yes, promote only prints the schedules.create preview.',
|
|
242
255
|
examples: [
|
|
243
256
|
'routines list',
|
|
244
257
|
'routines show daily-operations-sweep',
|
|
@@ -255,7 +268,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
255
268
|
},
|
|
256
269
|
auth: {
|
|
257
270
|
usage: ['auth', 'auth status', 'auth review', 'auth users', 'auth sessions'],
|
|
258
|
-
summary: 'Inspect Agent auth posture and
|
|
271
|
+
summary: 'Inspect Agent auth posture and connection token state. Runtime user/session administration stays outside Agent.',
|
|
259
272
|
examples: ['auth', 'auth status', 'auth users'],
|
|
260
273
|
},
|
|
261
274
|
compat: {
|
|
@@ -270,10 +283,13 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
270
283
|
'knowledge search <query> [--limit <n>]',
|
|
271
284
|
'knowledge list [--kind sources|nodes|issues] [--limit <n>]',
|
|
272
285
|
'knowledge get <id>',
|
|
273
|
-
'knowledge connectors',
|
|
286
|
+
'knowledge connectors [connectorId|doctor <connectorId>]',
|
|
274
287
|
'knowledge ingest-url <url> [--title <title>] [--tags a,b] --yes',
|
|
288
|
+
'knowledge ingest-file <path> [--title <title>] [--tags a,b] --yes',
|
|
289
|
+
'knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes',
|
|
275
290
|
'knowledge import-urls <path> --yes',
|
|
276
291
|
'knowledge import-bookmarks <path> --yes',
|
|
292
|
+
'knowledge import-browser-history [--browsers chrome,firefox] [--sources history,bookmark] --yes',
|
|
277
293
|
'knowledge reindex --yes',
|
|
278
294
|
],
|
|
279
295
|
summary: 'Call isolated Agent Knowledge/Wiki routes under /api/goodvibes-agent/knowledge. No default wiki or non-Agent fallback.',
|
|
@@ -283,8 +299,12 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
283
299
|
'knowledge search "release checklist"',
|
|
284
300
|
'knowledge list --kind sources',
|
|
285
301
|
'knowledge connectors',
|
|
302
|
+
'knowledge connectors doctor url',
|
|
286
303
|
'knowledge ingest-url https://example.com/page --title "Reference" --yes',
|
|
304
|
+
'knowledge ingest-file ./docs/guide.md --title "Guide" --yes',
|
|
305
|
+
'knowledge ingest-connector url --input https://example.com/reference --yes',
|
|
287
306
|
'knowledge import-urls ~/agent-links.txt --yes',
|
|
307
|
+
'knowledge import-browser-history --browsers chrome,firefox --sources history,bookmark --yes',
|
|
288
308
|
],
|
|
289
309
|
},
|
|
290
310
|
ask: {
|
|
@@ -322,7 +342,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
322
342
|
},
|
|
323
343
|
tasks: {
|
|
324
344
|
usage: ['tasks list', 'tasks show <taskId>'],
|
|
325
|
-
summary: 'Inspect
|
|
345
|
+
summary: 'Inspect connected-service task summaries. Agent blocks service-owned task submission; use run for one-shot work or delegate for explicit build/fix/review handoff.',
|
|
326
346
|
examples: ['tasks list', 'tasks show task-123', 'run "check provider readiness"', 'delegate "fix the failing tests"'],
|
|
327
347
|
},
|
|
328
348
|
bundle: {
|
|
@@ -343,7 +363,6 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
343
363
|
};
|
|
344
364
|
|
|
345
365
|
const HELP_ALIASES: Record<string, string> = {
|
|
346
|
-
app: 'tui',
|
|
347
366
|
exec: 'run',
|
|
348
367
|
onboarding: 'setup',
|
|
349
368
|
provider: 'providers',
|
|
@@ -279,7 +279,7 @@ export async function handleTasks(runtime: CliCommandRuntime): Promise<string> {
|
|
|
279
279
|
const [sub = 'list', ...rest] = runtime.cli.commandArgs;
|
|
280
280
|
if (sub === 'submit') {
|
|
281
281
|
return [
|
|
282
|
-
'GoodVibes Agent blocks CLI task submission from the
|
|
282
|
+
'GoodVibes Agent blocks CLI task submission from the service-owned task workflow.',
|
|
283
283
|
' policy: do normal assistant work in the main Agent conversation or use `goodvibes-agent run <prompt>` for an explicit one-shot run.',
|
|
284
284
|
' build/fix/review: use `goodvibes-agent delegate <task>` for explicit GoodVibes TUI handoff.',
|
|
285
285
|
' result: no local task was started.',
|
|
@@ -289,7 +289,7 @@ export async function handleTasks(runtime: CliCommandRuntime): Promise<string> {
|
|
|
289
289
|
const tasks = [...services.runtimeStore.getState().tasks.tasks.values()];
|
|
290
290
|
if (sub === 'list') {
|
|
291
291
|
return tasks.length === 0
|
|
292
|
-
? 'GoodVibes tasks\n No
|
|
292
|
+
? 'GoodVibes tasks\n No connected-service tasks are currently recorded.'
|
|
293
293
|
: ['GoodVibes tasks', ...tasks.map((task) => ` ${task.id} ${task.status} ${task.kind} ${task.title}`)].join('\n');
|
|
294
294
|
}
|
|
295
295
|
if (sub === 'show') {
|
package/src/cli/management.ts
CHANGED
|
@@ -544,9 +544,9 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
|
|
|
544
544
|
]);
|
|
545
545
|
if (blocked.has(sub)) {
|
|
546
546
|
return [
|
|
547
|
-
'Unsupported:
|
|
548
|
-
'GoodVibes Agent
|
|
549
|
-
'Use the
|
|
547
|
+
'Unsupported: connected-service auth user/session administration is outside GoodVibes Agent.',
|
|
548
|
+
'GoodVibes Agent does not create, delete, rotate, revoke, or clear connected-service users, sessions, or bootstrap credentials.',
|
|
549
|
+
'Use the owning GoodVibes host for auth administration.',
|
|
550
550
|
].join('\n');
|
|
551
551
|
}
|
|
552
552
|
if (sub !== 'status' && sub !== 'review' && sub !== 'list' && sub !== 'users' && sub !== 'sessions') {
|
|
@@ -566,21 +566,21 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
|
|
|
566
566
|
if (sub === 'users' || sub === 'sessions') {
|
|
567
567
|
return formatJsonOrText(runtime.cli)(value, [
|
|
568
568
|
`GoodVibes Agent auth ${sub}`,
|
|
569
|
-
' owner:
|
|
569
|
+
' owner: connected GoodVibes services',
|
|
570
570
|
` operator token: ${paths.operatorTokenPresent ? 'present' : 'missing'}`,
|
|
571
571
|
` operator token path: ${paths.operatorTokenPath}`,
|
|
572
|
-
` ${sub}: managed
|
|
573
|
-
' Agent does not enumerate or mutate
|
|
572
|
+
` ${sub}: managed outside Agent`,
|
|
573
|
+
' Agent does not enumerate or mutate connected-service users/sessions from the local CLI.',
|
|
574
574
|
].join('\n'));
|
|
575
575
|
}
|
|
576
576
|
return formatJsonOrText(runtime.cli)(value, [
|
|
577
577
|
'GoodVibes Agent auth',
|
|
578
|
-
' owner:
|
|
578
|
+
' owner: connected GoodVibes services',
|
|
579
579
|
` permission mode: ${String(value.permissionMode)}`,
|
|
580
580
|
` operator token: ${paths.operatorTokenPresent ? 'present' : 'missing'} (${paths.operatorTokenPath})`,
|
|
581
581
|
` compatibility user store: ${paths.userStorePresent ? 'present' : 'missing'} (${paths.userStorePath})`,
|
|
582
582
|
` compatibility bootstrap credential: ${paths.bootstrapCredentialPresent ? 'present' : 'missing'} (${paths.bootstrapCredentialPath})`,
|
|
583
|
-
'
|
|
583
|
+
' connected-service user/session administration: outside Agent',
|
|
584
584
|
' next: goodvibes-agent providers',
|
|
585
585
|
' next: goodvibes-agent subscription providers',
|
|
586
586
|
].join('\n'));
|