@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
|
@@ -1,13 +1,22 @@
|
|
|
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
1
|
import type { CliCommandOutput } from './types.ts';
|
|
7
2
|
import type { CliCommandRuntime } from './management.ts';
|
|
3
|
+
import {
|
|
4
|
+
commandValues,
|
|
5
|
+
delegationTaskValues,
|
|
6
|
+
hasFlag,
|
|
7
|
+
parseConnectorInput,
|
|
8
|
+
readFirstStringList,
|
|
9
|
+
readOptionValue,
|
|
10
|
+
readPositiveInt,
|
|
11
|
+
readSinceMs,
|
|
12
|
+
readStringList,
|
|
13
|
+
stripCommandFlag,
|
|
14
|
+
} from './agent-knowledge-args.ts';
|
|
8
15
|
import {
|
|
9
16
|
formatAsk,
|
|
10
17
|
formatBatchIngest,
|
|
18
|
+
formatConnector,
|
|
19
|
+
formatConnectorDoctor,
|
|
11
20
|
formatConnectors,
|
|
12
21
|
formatEntityList,
|
|
13
22
|
formatFailure,
|
|
@@ -18,100 +27,22 @@ import {
|
|
|
18
27
|
formatSearch,
|
|
19
28
|
formatStatus,
|
|
20
29
|
} from './agent-knowledge-format.ts';
|
|
30
|
+
import { AGENT_KNOWLEDGE_METHODS, DELEGATION_METHOD } from './agent-knowledge-methods.ts';
|
|
31
|
+
import {
|
|
32
|
+
createAgentSdk,
|
|
33
|
+
fetchDaemonStatus,
|
|
34
|
+
findDisallowedKnowledgeScopeFlag,
|
|
35
|
+
formatScopeFlagRejection,
|
|
36
|
+
getAgentKnowledgeJson,
|
|
37
|
+
isRecord,
|
|
38
|
+
postAgentKnowledgeJson,
|
|
39
|
+
readPackageMetadata,
|
|
40
|
+
readString,
|
|
41
|
+
resolveDaemonConnection,
|
|
42
|
+
runKnowledgeCall,
|
|
43
|
+
} from './agent-knowledge-runtime.ts';
|
|
21
44
|
import { formatJsonOrText, yesNo } from './management.ts';
|
|
22
45
|
|
|
23
|
-
type JsonRecord = Record<string, unknown>;
|
|
24
|
-
|
|
25
|
-
interface AgentDaemonConnection {
|
|
26
|
-
readonly baseUrl: string;
|
|
27
|
-
readonly token: string | null;
|
|
28
|
-
readonly tokenPath: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface AgentKnowledgeFailure {
|
|
32
|
-
readonly ok: false;
|
|
33
|
-
readonly kind: 'daemon_unavailable' | 'auth_required' | 'version_mismatch' | 'daemon_route_unavailable' | 'daemon_error';
|
|
34
|
-
readonly error: string;
|
|
35
|
-
readonly baseUrl: string;
|
|
36
|
-
readonly route: string;
|
|
37
|
-
readonly daemonVersion?: string;
|
|
38
|
-
readonly expectedSdkVersion?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface AgentKnowledgeSuccess<TData> {
|
|
42
|
-
readonly ok: true;
|
|
43
|
-
readonly kind: string;
|
|
44
|
-
readonly route: string;
|
|
45
|
-
readonly data: TData;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
type AgentKnowledgeResult<TData> = AgentKnowledgeSuccess<TData> | AgentKnowledgeFailure;
|
|
49
|
-
|
|
50
|
-
interface DaemonCallMethod {
|
|
51
|
-
readonly kind: string;
|
|
52
|
-
readonly route: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const AGENT_KNOWLEDGE_METHODS = {
|
|
56
|
-
status: {
|
|
57
|
-
kind: 'agentKnowledge.status',
|
|
58
|
-
route: '/api/goodvibes-agent/knowledge/status',
|
|
59
|
-
},
|
|
60
|
-
ask: {
|
|
61
|
-
kind: 'agentKnowledge.ask',
|
|
62
|
-
route: '/api/goodvibes-agent/knowledge/ask',
|
|
63
|
-
},
|
|
64
|
-
search: {
|
|
65
|
-
kind: 'agentKnowledge.search',
|
|
66
|
-
route: '/api/goodvibes-agent/knowledge/search',
|
|
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
|
-
},
|
|
92
|
-
ingestUrl: {
|
|
93
|
-
kind: 'agentKnowledge.ingest.url',
|
|
94
|
-
route: '/api/goodvibes-agent/knowledge/ingest/url',
|
|
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
|
-
},
|
|
108
|
-
} as const;
|
|
109
|
-
|
|
110
|
-
const DELEGATION_METHOD = {
|
|
111
|
-
kind: 'sessions.messages.create',
|
|
112
|
-
route: 'sessions.messages.create',
|
|
113
|
-
} as const;
|
|
114
|
-
|
|
115
46
|
interface DelegationResult {
|
|
116
47
|
readonly sessionId: string;
|
|
117
48
|
readonly message: unknown;
|
|
@@ -119,253 +50,6 @@ interface DelegationResult {
|
|
|
119
50
|
readonly wrfcRequested: boolean;
|
|
120
51
|
}
|
|
121
52
|
|
|
122
|
-
function isRecord(value: unknown): value is JsonRecord {
|
|
123
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function readString(record: JsonRecord | null, key: string): string | null {
|
|
127
|
-
const value = record?.[key];
|
|
128
|
-
return typeof value === 'string' ? value : null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function commandValues(args: readonly string[]): string[] {
|
|
132
|
-
const values: string[] = [];
|
|
133
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
134
|
-
const token = args[index]!;
|
|
135
|
-
if (!token.startsWith('--')) {
|
|
136
|
-
values.push(token);
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
if (!token.includes('=') && args[index + 1] && !args[index + 1]!.startsWith('--')) index += 1;
|
|
140
|
-
}
|
|
141
|
-
return values;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function delegationTaskValues(args: readonly string[]): string[] {
|
|
145
|
-
const values: string[] = [];
|
|
146
|
-
for (const token of args) {
|
|
147
|
-
if (token === '--wrfc') continue;
|
|
148
|
-
if (!token.startsWith('--')) values.push(token);
|
|
149
|
-
}
|
|
150
|
-
return values;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function readOptionValue(args: readonly string[], name: string): string | undefined {
|
|
154
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
155
|
-
const token = args[index]!;
|
|
156
|
-
if (token === name) {
|
|
157
|
-
const next = args[index + 1];
|
|
158
|
-
return next && !next.startsWith('--') ? next : undefined;
|
|
159
|
-
}
|
|
160
|
-
if (token.startsWith(`${name}=`)) return token.slice(name.length + 1);
|
|
161
|
-
}
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function readPositiveInt(args: readonly string[], name: string, fallback: number): number {
|
|
166
|
-
const raw = readOptionValue(args, name);
|
|
167
|
-
if (!raw) return fallback;
|
|
168
|
-
const parsed = Number.parseInt(raw, 10);
|
|
169
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function readStringList(args: readonly string[], name: string): readonly string[] {
|
|
173
|
-
const raw = readOptionValue(args, name);
|
|
174
|
-
if (!raw) return [];
|
|
175
|
-
return raw.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function hasFlag(args: readonly string[], flag: string): boolean {
|
|
179
|
-
return args.includes(flag);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function stripCommandFlag(args: readonly string[], flag: string): { readonly rest: readonly string[]; readonly present: boolean } {
|
|
183
|
-
const rest: string[] = [];
|
|
184
|
-
let present = false;
|
|
185
|
-
for (const arg of args) {
|
|
186
|
-
if (arg === flag) {
|
|
187
|
-
present = true;
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
rest.push(arg);
|
|
191
|
-
}
|
|
192
|
-
return { rest, present };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function readPackageMetadata(): { readonly version: string; readonly sdkVersion: string } {
|
|
196
|
-
return { version: VERSION, sdkVersion: SDK_VERSION };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function resolveDaemonConnection(runtime: CliCommandRuntime): AgentDaemonConnection {
|
|
200
|
-
const host = String(runtime.configManager.get('controlPlane.host') ?? '127.0.0.1');
|
|
201
|
-
const port = Number(runtime.configManager.get('controlPlane.port') ?? 3421);
|
|
202
|
-
const baseUrl = `http://${host}:${Number.isFinite(port) ? port : 3421}`;
|
|
203
|
-
const tokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
|
|
204
|
-
if (!existsSync(tokenPath)) return { baseUrl, token: null, tokenPath };
|
|
205
|
-
try {
|
|
206
|
-
const parsed = JSON.parse(readFileSync(tokenPath, 'utf-8')) as unknown;
|
|
207
|
-
const token = isRecord(parsed) && typeof parsed.token === 'string' ? parsed.token : null;
|
|
208
|
-
return { baseUrl, token, tokenPath };
|
|
209
|
-
} catch {
|
|
210
|
-
return { baseUrl, token: null, tokenPath };
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{ readonly ok: boolean; readonly status: number; readonly body: unknown }> {
|
|
215
|
-
try {
|
|
216
|
-
const response = await fetch(`${connection.baseUrl}/status`, {
|
|
217
|
-
headers: connection.token ? { authorization: `Bearer ${connection.token}` } : undefined,
|
|
218
|
-
});
|
|
219
|
-
const text = await response.text();
|
|
220
|
-
let body: unknown = text;
|
|
221
|
-
try {
|
|
222
|
-
body = JSON.parse(text) as unknown;
|
|
223
|
-
} catch {
|
|
224
|
-
body = text;
|
|
225
|
-
}
|
|
226
|
-
return { ok: response.ok, status: response.status, body };
|
|
227
|
-
} catch (error) {
|
|
228
|
-
return { ok: false, status: 0, body: summarizeError(error) };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function classifyKnowledgeError(error: unknown, connection: AgentDaemonConnection, route: string): Promise<AgentKnowledgeFailure> {
|
|
233
|
-
const message = summarizeError(error);
|
|
234
|
-
const lower = message.toLowerCase();
|
|
235
|
-
if (lower.includes('401') || lower.includes('unauthorized') || lower.includes('auth')) {
|
|
236
|
-
return { ok: false, kind: 'auth_required', error: message, baseUrl: connection.baseUrl, route };
|
|
237
|
-
}
|
|
238
|
-
if (lower.includes('404') || lower.includes('not found')) {
|
|
239
|
-
const metadata = readPackageMetadata();
|
|
240
|
-
const daemon = await fetchDaemonStatus(connection);
|
|
241
|
-
const daemonRecord = isRecord(daemon.body) ? daemon.body : {};
|
|
242
|
-
const daemonVersion = readString(daemonRecord, 'version') ?? 'unknown';
|
|
243
|
-
if (daemon.ok && daemonVersion !== metadata.sdkVersion) {
|
|
244
|
-
return {
|
|
245
|
-
ok: false,
|
|
246
|
-
kind: 'version_mismatch',
|
|
247
|
-
error: `External runtime SDK version ${daemonVersion} does not match Agent SDK pin ${metadata.sdkVersion}; Agent Knowledge route is unavailable.`,
|
|
248
|
-
baseUrl: connection.baseUrl,
|
|
249
|
-
route,
|
|
250
|
-
daemonVersion,
|
|
251
|
-
expectedSdkVersion: metadata.sdkVersion,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
return { ok: false, kind: 'daemon_route_unavailable', error: message, baseUrl: connection.baseUrl, route };
|
|
255
|
-
}
|
|
256
|
-
if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
|
|
257
|
-
return { ok: false, kind: 'daemon_unavailable', error: message, baseUrl: connection.baseUrl, route };
|
|
258
|
-
}
|
|
259
|
-
return { ok: false, kind: 'daemon_error', error: message, baseUrl: connection.baseUrl, route };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function createAgentSdk(connection: AgentDaemonConnection) {
|
|
263
|
-
return createBrowserAgentSdk({
|
|
264
|
-
baseUrl: connection.baseUrl,
|
|
265
|
-
authToken: connection.token,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async function postAgentKnowledgeJson<TData>(
|
|
270
|
-
connection: AgentDaemonConnection,
|
|
271
|
-
route: string,
|
|
272
|
-
body: JsonRecord,
|
|
273
|
-
): Promise<TData> {
|
|
274
|
-
const response = await fetch(`${connection.baseUrl}${route}`, {
|
|
275
|
-
method: 'POST',
|
|
276
|
-
headers: {
|
|
277
|
-
authorization: `Bearer ${connection.token ?? ''}`,
|
|
278
|
-
'content-type': 'application/json',
|
|
279
|
-
},
|
|
280
|
-
body: JSON.stringify(body),
|
|
281
|
-
});
|
|
282
|
-
const text = await response.text();
|
|
283
|
-
let parsed: unknown = text;
|
|
284
|
-
if (text.trim()) {
|
|
285
|
-
try {
|
|
286
|
-
parsed = JSON.parse(text) as unknown;
|
|
287
|
-
} catch {
|
|
288
|
-
parsed = text;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (!response.ok) {
|
|
292
|
-
const detail = isRecord(parsed) && typeof parsed.error === 'string' ? parsed.error : text;
|
|
293
|
-
throw new Error(`HTTP ${response.status} ${response.statusText}${detail ? `: ${detail}` : ''}`);
|
|
294
|
-
}
|
|
295
|
-
return parsed as TData;
|
|
296
|
-
}
|
|
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
|
-
|
|
342
|
-
function findDisallowedKnowledgeScopeFlag(args: readonly string[]): string | null {
|
|
343
|
-
const disallowed = [
|
|
344
|
-
'--space',
|
|
345
|
-
'--knowledge-space',
|
|
346
|
-
'--knowledge-space-id',
|
|
347
|
-
['--knowledge', 'SpaceId'].join(''),
|
|
348
|
-
'--include-all-spaces',
|
|
349
|
-
['--include', 'AllSpaces'].join(''),
|
|
350
|
-
['--home', 'graph'].join(''),
|
|
351
|
-
['--home', '-graph'].join(''),
|
|
352
|
-
];
|
|
353
|
-
for (const token of args) {
|
|
354
|
-
for (const flag of disallowed) {
|
|
355
|
-
if (token === flag || token.startsWith(`${flag}=`)) return flag;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function formatScopeFlagRejection(flag: string): string {
|
|
362
|
-
return [
|
|
363
|
-
`Agent Knowledge is isolated; ${flag} is not accepted.`,
|
|
364
|
-
'GoodVibes Agent must not use default Knowledge/Wiki or non-Agent product spaces.',
|
|
365
|
-
'Use only /api/goodvibes-agent/knowledge/* Agent-owned routes.',
|
|
366
|
-
].join('\n');
|
|
367
|
-
}
|
|
368
|
-
|
|
369
53
|
function buildDelegationBody(task: string, wrfcRequested: boolean): string {
|
|
370
54
|
return [
|
|
371
55
|
'GoodVibes Agent explicit build delegation.',
|
|
@@ -383,29 +67,6 @@ function buildDelegationBody(task: string, wrfcRequested: boolean): string {
|
|
|
383
67
|
].join('\n');
|
|
384
68
|
}
|
|
385
69
|
|
|
386
|
-
async function runKnowledgeCall<TData>(
|
|
387
|
-
runtime: CliCommandRuntime,
|
|
388
|
-
method: DaemonCallMethod,
|
|
389
|
-
call: (connection: AgentDaemonConnection) => Promise<TData>,
|
|
390
|
-
): Promise<AgentKnowledgeResult<TData>> {
|
|
391
|
-
const connection = resolveDaemonConnection(runtime);
|
|
392
|
-
if (!connection.token) {
|
|
393
|
-
return {
|
|
394
|
-
ok: false,
|
|
395
|
-
kind: 'auth_required',
|
|
396
|
-
error: `No runtime operator token found at ${connection.tokenPath}`,
|
|
397
|
-
baseUrl: connection.baseUrl,
|
|
398
|
-
route: method.route,
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
try {
|
|
402
|
-
const data = await call(connection);
|
|
403
|
-
return { ok: true, kind: method.kind, route: method.route, data };
|
|
404
|
-
} catch (error) {
|
|
405
|
-
return classifyKnowledgeError(error, connection, method.route);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
70
|
export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
|
|
410
71
|
const [sub = 'status', ...rawRest] = runtime.cli.commandArgs;
|
|
411
72
|
const confirmation = stripCommandFlag(rawRest, '--yes');
|
|
@@ -521,6 +182,32 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
521
182
|
}
|
|
522
183
|
|
|
523
184
|
if (normalized === 'connectors') {
|
|
185
|
+
const values = commandValues(rest);
|
|
186
|
+
if (values[0] === 'doctor') {
|
|
187
|
+
const id = values[1];
|
|
188
|
+
if (!id) return { output: 'Usage: goodvibes-agent knowledge connectors doctor <connectorId>', exitCode: 2 };
|
|
189
|
+
const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}/doctor`;
|
|
190
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorDoctor, async (connection) => (
|
|
191
|
+
await getAgentKnowledgeJson(connection, route)
|
|
192
|
+
));
|
|
193
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
194
|
+
return {
|
|
195
|
+
output: formatJsonOrText(runtime.cli)(result, formatConnectorDoctor(result.data, id)),
|
|
196
|
+
exitCode: 0,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const id = values[0];
|
|
200
|
+
if (id) {
|
|
201
|
+
const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}`;
|
|
202
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorGet, async (connection) => (
|
|
203
|
+
await getAgentKnowledgeJson(connection, route)
|
|
204
|
+
));
|
|
205
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
206
|
+
return {
|
|
207
|
+
output: formatJsonOrText(runtime.cli)(result, formatConnector(result.data, id)),
|
|
208
|
+
exitCode: 0,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
524
211
|
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorsList, async (connection) => (
|
|
525
212
|
await getAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.connectorsList.route)
|
|
526
213
|
));
|
|
@@ -531,6 +218,34 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
531
218
|
};
|
|
532
219
|
}
|
|
533
220
|
|
|
221
|
+
if (normalized === 'connector' || normalized === 'connector-get') {
|
|
222
|
+
const [id] = commandValues(rest);
|
|
223
|
+
if (!id) return { output: 'Usage: goodvibes-agent knowledge connector <connectorId>', exitCode: 2 };
|
|
224
|
+
const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}`;
|
|
225
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorGet, async (connection) => (
|
|
226
|
+
await getAgentKnowledgeJson(connection, route)
|
|
227
|
+
));
|
|
228
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
229
|
+
return {
|
|
230
|
+
output: formatJsonOrText(runtime.cli)(result, formatConnector(result.data, id)),
|
|
231
|
+
exitCode: 0,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (normalized === 'connector-doctor' || normalized === 'doctor-connector') {
|
|
236
|
+
const [id] = commandValues(rest);
|
|
237
|
+
if (!id) return { output: 'Usage: goodvibes-agent knowledge connector-doctor <connectorId>', exitCode: 2 };
|
|
238
|
+
const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}/doctor`;
|
|
239
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorDoctor, async (connection) => (
|
|
240
|
+
await getAgentKnowledgeJson(connection, route)
|
|
241
|
+
));
|
|
242
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
243
|
+
return {
|
|
244
|
+
output: formatJsonOrText(runtime.cli)(result, formatConnectorDoctor(result.data, id)),
|
|
245
|
+
exitCode: 0,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
534
249
|
if (normalized === 'ingest-url') {
|
|
535
250
|
const values = commandValues(rest);
|
|
536
251
|
const url = values[0];
|
|
@@ -565,6 +280,50 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
565
280
|
};
|
|
566
281
|
}
|
|
567
282
|
|
|
283
|
+
if (normalized === 'ingest-file' || normalized === 'ingest-artifact') {
|
|
284
|
+
const values = commandValues(rest);
|
|
285
|
+
const path = values[0];
|
|
286
|
+
if (!path) return { output: 'Usage: goodvibes-agent knowledge ingest-file <path> [--title <title>] [--tags a,b] [--folder <path>] --yes', exitCode: 2 };
|
|
287
|
+
if (!confirmation.present) {
|
|
288
|
+
const failure = {
|
|
289
|
+
ok: false,
|
|
290
|
+
kind: 'confirmation_required',
|
|
291
|
+
error: `Refusing to ingest file into Agent Knowledge ${path} without --yes.`,
|
|
292
|
+
route: AGENT_KNOWLEDGE_METHODS.ingestArtifact.route,
|
|
293
|
+
};
|
|
294
|
+
return {
|
|
295
|
+
output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge ingest-file <path> [--title <title>] [--tags a,b] [--folder <path>] --yes`,
|
|
296
|
+
exitCode: 2,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const title = readOptionValue(rest, '--title');
|
|
300
|
+
const tags = readStringList(rest, '--tags');
|
|
301
|
+
const folderPath = readOptionValue(rest, '--folder');
|
|
302
|
+
const connectorId = readOptionValue(rest, '--connector') ?? 'goodvibes-agent-file';
|
|
303
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.ingestArtifact, async (connection) => (
|
|
304
|
+
await postAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.ingestArtifact.route, {
|
|
305
|
+
path,
|
|
306
|
+
title,
|
|
307
|
+
tags,
|
|
308
|
+
folderPath,
|
|
309
|
+
connectorId,
|
|
310
|
+
allowPrivateHosts: hasFlag(rest, '--allow-private-hosts'),
|
|
311
|
+
metadata: { originSurface: 'goodvibes-agent-cli' },
|
|
312
|
+
})
|
|
313
|
+
));
|
|
314
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
315
|
+
return {
|
|
316
|
+
output: formatJsonOrText(runtime.cli)(result, formatIngest(
|
|
317
|
+
result.data,
|
|
318
|
+
path,
|
|
319
|
+
'ingest-file',
|
|
320
|
+
'/api/goodvibes-agent/knowledge/ingest/artifact',
|
|
321
|
+
'file',
|
|
322
|
+
)),
|
|
323
|
+
exitCode: 0,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
568
327
|
if (normalized === 'import-urls' || normalized === 'import-bookmarks') {
|
|
569
328
|
const values = commandValues(rest);
|
|
570
329
|
const path = values[0];
|
|
@@ -603,6 +362,78 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
603
362
|
};
|
|
604
363
|
}
|
|
605
364
|
|
|
365
|
+
if (normalized === 'import-browser-history' || normalized === 'sync-browser-history') {
|
|
366
|
+
if (!confirmation.present) {
|
|
367
|
+
const failure = {
|
|
368
|
+
ok: false,
|
|
369
|
+
kind: 'confirmation_required',
|
|
370
|
+
error: 'Refusing to import browser history into Agent Knowledge without --yes.',
|
|
371
|
+
route: AGENT_KNOWLEDGE_METHODS.ingestBrowserHistory.route,
|
|
372
|
+
};
|
|
373
|
+
return {
|
|
374
|
+
output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge import-browser-history [--browsers chrome,firefox] [--sources history,bookmark] [--limit <n>] [--since-days <n>] --yes`,
|
|
375
|
+
exitCode: 2,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const browsers = readFirstStringList(rest, ['--browsers', '--browser']);
|
|
379
|
+
const sourceKinds = readFirstStringList(rest, ['--sources', '--source-kinds', '--source-kind']);
|
|
380
|
+
const homeOverride = readOptionValue(rest, '--home');
|
|
381
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.ingestBrowserHistory, async (connection) => (
|
|
382
|
+
await postAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.ingestBrowserHistory.route, {
|
|
383
|
+
browsers,
|
|
384
|
+
sourceKinds,
|
|
385
|
+
homeOverride,
|
|
386
|
+
limit: readPositiveInt(rest, '--limit', 250),
|
|
387
|
+
sinceMs: readSinceMs(rest),
|
|
388
|
+
connectorId: 'goodvibes-agent-browser-history',
|
|
389
|
+
})
|
|
390
|
+
));
|
|
391
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
392
|
+
return {
|
|
393
|
+
output: formatJsonOrText(runtime.cli)(result, formatBatchIngest(result.data, 'browser-history')),
|
|
394
|
+
exitCode: 0,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (normalized === 'ingest-connector') {
|
|
399
|
+
const values = commandValues(rest);
|
|
400
|
+
const connectorId = values[0];
|
|
401
|
+
if (!connectorId) return { output: 'Usage: goodvibes-agent knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes', exitCode: 2 };
|
|
402
|
+
const input = parseConnectorInput(readOptionValue(rest, '--input'));
|
|
403
|
+
const path = readOptionValue(rest, '--path');
|
|
404
|
+
const content = readOptionValue(rest, '--content');
|
|
405
|
+
if (input === undefined && !path && !content) {
|
|
406
|
+
return { output: 'Usage: goodvibes-agent knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes', exitCode: 2 };
|
|
407
|
+
}
|
|
408
|
+
if (!confirmation.present) {
|
|
409
|
+
const failure = {
|
|
410
|
+
ok: false,
|
|
411
|
+
kind: 'confirmation_required',
|
|
412
|
+
error: `Refusing to ingest connector input into Agent Knowledge for ${connectorId} without --yes.`,
|
|
413
|
+
route: AGENT_KNOWLEDGE_METHODS.ingestConnector.route,
|
|
414
|
+
};
|
|
415
|
+
return {
|
|
416
|
+
output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes`,
|
|
417
|
+
exitCode: 2,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.ingestConnector, async (connection) => (
|
|
421
|
+
await postAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.ingestConnector.route, {
|
|
422
|
+
connectorId,
|
|
423
|
+
input,
|
|
424
|
+
path,
|
|
425
|
+
content,
|
|
426
|
+
allowPrivateHosts: hasFlag(rest, '--allow-private-hosts'),
|
|
427
|
+
sessionId: 'goodvibes-agent-cli',
|
|
428
|
+
})
|
|
429
|
+
));
|
|
430
|
+
if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
|
|
431
|
+
return {
|
|
432
|
+
output: formatJsonOrText(runtime.cli)(result, formatBatchIngest(result.data, `connector ${connectorId}`)),
|
|
433
|
+
exitCode: 0,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
606
437
|
if (normalized === 'reindex') {
|
|
607
438
|
if (!confirmation.present) {
|
|
608
439
|
const failure = {
|
|
@@ -627,7 +458,7 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
627
458
|
}
|
|
628
459
|
|
|
629
460
|
return {
|
|
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]',
|
|
461
|
+
output: 'Usage: goodvibes-agent knowledge [status|ask <question>|search <query>|list|sources|nodes|issues|get <id>|map|connectors|ingest-url <url> --yes|ingest-file <path> --yes|ingest-connector <id> --yes|import-urls <path> --yes|import-bookmarks <path> --yes|import-browser-history --yes|reindex --yes]',
|
|
631
462
|
exitCode: 2,
|
|
632
463
|
};
|
|
633
464
|
}
|
|
@@ -687,7 +518,7 @@ export async function handleCompatCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
687
518
|
` version compatible: ${yesNo(versionCompatible)}`,
|
|
688
519
|
` operator token: ${connection.token ? 'present' : 'missing'} (${connection.tokenPath})`,
|
|
689
520
|
` Agent knowledge route: ${knowledgeRouteReady ? 'ready' : `not ready (${knowledgeRoute.ok ? 'unknown' : knowledgeRoute.kind})`}`,
|
|
690
|
-
...(versionCompatible ? [] : [' next: update
|
|
521
|
+
...(versionCompatible ? [] : [' next: update connected GoodVibes services so /status matches the Agent SDK pin.']),
|
|
691
522
|
].join('\n');
|
|
692
523
|
return {
|
|
693
524
|
output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text,
|