@pellux/goodvibes-agent 0.1.0 → 0.1.2

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 CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to GoodVibes Agent will be recorded here.
4
4
 
5
+ ## 0.1.2 - 2026-05-30
6
+
7
+ - Added `goodvibes-agent compat` for package SDK pin, external daemon version, auth presence, and isolated Agent Knowledge route readiness.
8
+ - Added `goodvibes-agent knowledge ...` commands for the isolated `/api/goodvibes-agent/knowledge/*` environment with no default Knowledge/Wiki or HomeGraph fallback.
9
+ - Added explicit GoodVibes TUI build delegation through `goodvibes-agent delegate` and `/delegate`; WRFC is requested only through explicit `--wrfc`, `/wrfc`, or `/review` delegation.
10
+ - Removed the copied WRFC panel from the default Agent panel registry while preserving explicit TUI delegation for build/fix/review work.
11
+ - Hardened the Agent release helper and CLI help output for the current Agent changelog and command set.
12
+
13
+ ## 0.1.1 - 2026-05-30
14
+
15
+ - Reissued the first public alpha package after the initial `0.1.0` registry publish produced an install-blocking npm packument inconsistency.
16
+ - Kept the same Agent runtime boundary and TUI-derived shell foundation: external daemon only, serial/proactive Agent policy, and explicit GoodVibes TUI delegation for build/fix/review work.
17
+
5
18
  ## 0.1.0 - 2026-05-28
6
19
 
7
20
  - Published the first public alpha package for `@pellux/goodvibes-agent`.
@@ -1,6 +1,6 @@
1
1
  # Getting Started
2
2
 
3
- GoodVibes Agent `0.1.0` is the first public alpha of the personal operator assistant built on the GoodVibes TUI foundation.
3
+ GoodVibes Agent `0.1.2` is the current installable public alpha of the personal operator assistant built on the GoodVibes TUI foundation.
4
4
 
5
5
  ## Requirements
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Release And Publishing
2
2
 
3
- GoodVibes Agent `0.1.0` is the first public alpha release.
3
+ GoodVibes Agent `0.1.2` is the current installable public alpha release.
4
4
 
5
5
  ## Package Identity
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "description": "Near-fork GoodVibes operator assistant with the GoodVibes TUI shell, renderer, input, fullscreen workspace, and daemon-connected Agent product brain.",
6
6
  "type": "module",
@@ -0,0 +1,525 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { createBrowserAgentSdk } from '@pellux/goodvibes-sdk/browser/agent';
5
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
6
+ import type { CliCommandOutput } from './types.ts';
7
+ import type { CliCommandRuntime } from './management.ts';
8
+ import { formatJsonOrText, yesNo } from './management.ts';
9
+
10
+ type JsonRecord = Record<string, unknown>;
11
+
12
+ interface AgentDaemonConnection {
13
+ readonly baseUrl: string;
14
+ readonly token: string | null;
15
+ readonly tokenPath: string;
16
+ }
17
+
18
+ interface AgentKnowledgeFailure {
19
+ readonly ok: false;
20
+ readonly kind: 'daemon_unavailable' | 'auth_required' | 'daemon_route_unavailable' | 'daemon_error';
21
+ readonly error: string;
22
+ readonly baseUrl: string;
23
+ readonly route: string;
24
+ }
25
+
26
+ interface AgentKnowledgeSuccess<TData> {
27
+ readonly ok: true;
28
+ readonly kind: string;
29
+ readonly route: string;
30
+ readonly data: TData;
31
+ }
32
+
33
+ type AgentKnowledgeResult<TData> = AgentKnowledgeSuccess<TData> | AgentKnowledgeFailure;
34
+
35
+ function isRecord(value: unknown): value is JsonRecord {
36
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
37
+ }
38
+
39
+ function readString(record: JsonRecord | null, key: string): string | null {
40
+ const value = record?.[key];
41
+ return typeof value === 'string' ? value : null;
42
+ }
43
+
44
+ function readNumber(record: JsonRecord | null, key: string): number | null {
45
+ const value = record?.[key];
46
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
47
+ }
48
+
49
+ function readBoolean(record: JsonRecord | null, key: string): boolean | null {
50
+ const value = record?.[key];
51
+ return typeof value === 'boolean' ? value : null;
52
+ }
53
+
54
+ function readArray(record: JsonRecord | null, key: string): readonly unknown[] {
55
+ const value = record?.[key];
56
+ return Array.isArray(value) ? value : [];
57
+ }
58
+
59
+ function cleanInline(value: unknown): string {
60
+ return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
61
+ }
62
+
63
+ function commandValues(args: readonly string[]): string[] {
64
+ const values: string[] = [];
65
+ for (let index = 0; index < args.length; index += 1) {
66
+ const token = args[index]!;
67
+ if (!token.startsWith('--')) {
68
+ values.push(token);
69
+ continue;
70
+ }
71
+ if (!token.includes('=') && args[index + 1] && !args[index + 1]!.startsWith('--')) index += 1;
72
+ }
73
+ return values;
74
+ }
75
+
76
+ function delegationTaskValues(args: readonly string[]): string[] {
77
+ const values: string[] = [];
78
+ for (const token of args) {
79
+ if (token === '--wrfc') continue;
80
+ if (!token.startsWith('--')) values.push(token);
81
+ }
82
+ return values;
83
+ }
84
+
85
+ function readOptionValue(args: readonly string[], name: string): string | undefined {
86
+ for (let index = 0; index < args.length; index += 1) {
87
+ const token = args[index]!;
88
+ if (token === name) {
89
+ const next = args[index + 1];
90
+ return next && !next.startsWith('--') ? next : undefined;
91
+ }
92
+ if (token.startsWith(`${name}=`)) return token.slice(name.length + 1);
93
+ }
94
+ return undefined;
95
+ }
96
+
97
+ function readPositiveInt(args: readonly string[], name: string, fallback: number): number {
98
+ const raw = readOptionValue(args, name);
99
+ if (!raw) return fallback;
100
+ const parsed = Number.parseInt(raw, 10);
101
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
102
+ }
103
+
104
+ function readStringList(args: readonly string[], name: string): readonly string[] {
105
+ const raw = readOptionValue(args, name);
106
+ if (!raw) return [];
107
+ return raw.split(',').map((entry) => entry.trim()).filter(Boolean);
108
+ }
109
+
110
+ function hasFlag(args: readonly string[], flag: string): boolean {
111
+ return args.includes(flag);
112
+ }
113
+
114
+ function packageJsonPath(): string {
115
+ return join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
116
+ }
117
+
118
+ function readPackageMetadata(): { readonly version: string; readonly sdkVersion: string } {
119
+ try {
120
+ const parsed = JSON.parse(readFileSync(packageJsonPath(), 'utf-8')) as unknown;
121
+ if (!isRecord(parsed)) return { version: 'unknown', sdkVersion: 'unknown' };
122
+ const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
123
+ return {
124
+ version: typeof parsed.version === 'string' ? parsed.version : 'unknown',
125
+ sdkVersion: typeof dependencies['@pellux/goodvibes-sdk'] === 'string'
126
+ ? dependencies['@pellux/goodvibes-sdk']
127
+ : 'unknown',
128
+ };
129
+ } catch {
130
+ return { version: 'unknown', sdkVersion: 'unknown' };
131
+ }
132
+ }
133
+
134
+ function resolveDaemonConnection(runtime: CliCommandRuntime): AgentDaemonConnection {
135
+ const host = String(runtime.configManager.get('controlPlane.host') ?? '127.0.0.1');
136
+ const port = Number(runtime.configManager.get('controlPlane.port') ?? 3421);
137
+ const baseUrl = `http://${host}:${Number.isFinite(port) ? port : 3421}`;
138
+ const tokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
139
+ if (!existsSync(tokenPath)) return { baseUrl, token: null, tokenPath };
140
+ try {
141
+ const parsed = JSON.parse(readFileSync(tokenPath, 'utf-8')) as unknown;
142
+ const token = isRecord(parsed) && typeof parsed.token === 'string' ? parsed.token : null;
143
+ return { baseUrl, token, tokenPath };
144
+ } catch {
145
+ return { baseUrl, token: null, tokenPath };
146
+ }
147
+ }
148
+
149
+ async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{ readonly ok: boolean; readonly status: number; readonly body: unknown }> {
150
+ try {
151
+ const response = await fetch(`${connection.baseUrl}/status`, {
152
+ headers: connection.token ? { authorization: `Bearer ${connection.token}` } : undefined,
153
+ });
154
+ const text = await response.text();
155
+ let body: unknown = text;
156
+ try {
157
+ body = JSON.parse(text) as unknown;
158
+ } catch {
159
+ body = text;
160
+ }
161
+ return { ok: response.ok, status: response.status, body };
162
+ } catch (error) {
163
+ return { ok: false, status: 0, body: summarizeError(error) };
164
+ }
165
+ }
166
+
167
+ function classifyKnowledgeError(error: unknown, connection: AgentDaemonConnection, route: string): AgentKnowledgeFailure {
168
+ const message = summarizeError(error);
169
+ const lower = message.toLowerCase();
170
+ if (lower.includes('401') || lower.includes('unauthorized') || lower.includes('auth')) {
171
+ return { ok: false, kind: 'auth_required', error: message, baseUrl: connection.baseUrl, route };
172
+ }
173
+ if (lower.includes('404') || lower.includes('not found')) {
174
+ return { ok: false, kind: 'daemon_route_unavailable', error: message, baseUrl: connection.baseUrl, route };
175
+ }
176
+ if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
177
+ return { ok: false, kind: 'daemon_unavailable', error: message, baseUrl: connection.baseUrl, route };
178
+ }
179
+ return { ok: false, kind: 'daemon_error', error: message, baseUrl: connection.baseUrl, route };
180
+ }
181
+
182
+ function createAgentSdk(connection: AgentDaemonConnection) {
183
+ return createBrowserAgentSdk({
184
+ baseUrl: connection.baseUrl,
185
+ authToken: connection.token,
186
+ });
187
+ }
188
+
189
+ function sourceLine(value: unknown): string {
190
+ const record = isRecord(value) ? value : {};
191
+ const title = cleanInline(record.title)
192
+ || cleanInline(record.canonicalUri)
193
+ || cleanInline(record.sourceUri)
194
+ || cleanInline(record.url)
195
+ || cleanInline(record.id)
196
+ || 'untitled';
197
+ const type = cleanInline(record.sourceType) || cleanInline(record.type) || 'source';
198
+ const url = cleanInline(record.canonicalUri) || cleanInline(record.sourceUri) || cleanInline(record.url);
199
+ return url && url !== title ? `[${type}] ${title} (${url})` : `[${type}] ${title}`;
200
+ }
201
+
202
+ function resultLine(value: unknown): string {
203
+ const record = isRecord(value) ? value : {};
204
+ const title = cleanInline(record.title) || cleanInline(record.id) || 'untitled';
205
+ const id = cleanInline(record.id);
206
+ const type = cleanInline(record.type) || cleanInline(record.kind) || cleanInline(record.sourceType) || 'result';
207
+ const score = readNumber(record, 'score');
208
+ const url = cleanInline(record.url) || cleanInline(record.canonicalUri) || cleanInline(record.sourceUri);
209
+ const snippet = cleanInline(record.snippet) || cleanInline(record.summary) || cleanInline(record.text);
210
+ const parts = [
211
+ `[${type}] ${title}`,
212
+ id && id !== title ? `id=${id}` : '',
213
+ score !== null ? `score=${score.toFixed(3)}` : '',
214
+ url ? `url=${url}` : '',
215
+ ].filter((part) => part.length > 0);
216
+ return snippet ? `${parts.join(' ')}\n ${snippet}` : parts.join(' ');
217
+ }
218
+
219
+ function formatStatus(data: unknown): string {
220
+ const record = isRecord(data) ? data : {};
221
+ const ready = readBoolean(record, 'ready');
222
+ const sourceCount = readNumber(record, 'sourceCount');
223
+ const nodeCount = readNumber(record, 'nodeCount');
224
+ const issueCount = readNumber(record, 'issueCount');
225
+ const edgeCount = readNumber(record, 'edgeCount');
226
+ const storagePath = readString(record, 'storagePath');
227
+ return [
228
+ 'Agent Knowledge status',
229
+ ` ready: ${ready === null ? 'unknown' : yesNo(ready)}`,
230
+ ` sources: ${sourceCount ?? 'unknown'}`,
231
+ ` nodes: ${nodeCount ?? 'unknown'}`,
232
+ ` edges: ${edgeCount ?? 'unknown'}`,
233
+ ` issues: ${issueCount ?? 'unknown'}`,
234
+ storagePath ? ` storage: ${storagePath}` : null,
235
+ ' route: /api/goodvibes-agent/knowledge/status',
236
+ ].filter((line): line is string => Boolean(line)).join('\n');
237
+ }
238
+
239
+ function formatAsk(data: unknown, query: string): string {
240
+ const record = isRecord(data) ? data : {};
241
+ const answer = isRecord(record.answer) ? record.answer : record;
242
+ const text = cleanInline(answer.text) || cleanInline(record.answer) || 'No answer returned.';
243
+ const confidence = readNumber(answer, 'confidence') ?? readNumber(record, 'confidence');
244
+ const synthesized = readBoolean(answer, 'synthesized');
245
+ const sources = readArray(answer, 'sources');
246
+ const facts = readArray(answer, 'facts');
247
+ const gaps = readArray(answer, 'gaps');
248
+ const lines = [
249
+ `Agent Knowledge answer: ${query}`,
250
+ text,
251
+ '',
252
+ `confidence: ${confidence ?? 'unknown'}${synthesized === null ? '' : ` synthesized: ${yesNo(synthesized)}`}`,
253
+ ];
254
+ if (sources.length > 0) {
255
+ lines.push('', 'Sources:', ...sources.slice(0, 8).map((source) => ` - ${sourceLine(source)}`));
256
+ }
257
+ if (facts.length > 0) lines.push('', `Facts: ${facts.length}`);
258
+ if (gaps.length > 0) lines.push('', `Gaps: ${gaps.length}`);
259
+ return lines.join('\n');
260
+ }
261
+
262
+ function formatSearch(data: unknown, query: string): string {
263
+ const record = isRecord(data) ? data : {};
264
+ const items = readArray(record, 'items');
265
+ const results = items.length > 0 ? items : readArray(record, 'results');
266
+ if (results.length === 0) {
267
+ return [
268
+ `Agent Knowledge search: ${query}`,
269
+ ' no results',
270
+ ' route: /api/goodvibes-agent/knowledge/search',
271
+ ].join('\n');
272
+ }
273
+ return [
274
+ `Agent Knowledge search: ${query}`,
275
+ ...results.slice(0, 10).map((result, index) => ` ${index + 1}. ${resultLine(result)}`),
276
+ ].join('\n');
277
+ }
278
+
279
+ function formatIngest(data: unknown, url: string): string {
280
+ const record = isRecord(data) ? data : {};
281
+ const source = isRecord(record.source) ? record.source : record;
282
+ const sourceId = cleanInline(source.id);
283
+ const canonicalUri = cleanInline(source.canonicalUri) || cleanInline(source.sourceUri) || url;
284
+ const artifactId = cleanInline(record.artifactId);
285
+ return [
286
+ 'Agent Knowledge ingest-url accepted',
287
+ ` source: ${sourceId || '(pending)'}`,
288
+ ` url: ${canonicalUri}`,
289
+ artifactId ? ` artifact: ${artifactId}` : null,
290
+ ' route: /api/goodvibes-agent/knowledge/ingest/url',
291
+ ].filter((line): line is string => Boolean(line)).join('\n');
292
+ }
293
+
294
+ function buildDelegationBody(task: string, wrfcRequested: boolean): string {
295
+ return [
296
+ 'GoodVibes Agent explicit build delegation.',
297
+ '',
298
+ 'Original user ask:',
299
+ task,
300
+ '',
301
+ 'Agent policy:',
302
+ '- GoodVibes Agent is not the coding TUI.',
303
+ '- Preserve the full original ask.',
304
+ '- GoodVibes TUI owns file edits, git/worktree flows, sandbox/QEMU UX, and any WRFC owner chain.',
305
+ wrfcRequested
306
+ ? '- WRFC was explicitly requested by the Agent user for this build/fix/review delegation.'
307
+ : '- WRFC was not explicitly requested; do not turn this into WRFC solely because it came from Agent.',
308
+ ].join('\n');
309
+ }
310
+
311
+ function formatFailure(failure: AgentKnowledgeFailure, json: boolean): string {
312
+ if (json) return JSON.stringify(failure, null, 2);
313
+ return [
314
+ `Agent Knowledge error: ${failure.kind}`,
315
+ ` ${failure.error}`,
316
+ ` daemon: ${failure.baseUrl}`,
317
+ ` route: ${failure.route}`,
318
+ failure.kind === 'daemon_route_unavailable'
319
+ ? ' next: update/restart the external GoodVibes daemon to the SDK version required by this Agent package.'
320
+ : null,
321
+ ].filter((line): line is string => Boolean(line)).join('\n');
322
+ }
323
+
324
+ async function runKnowledgeCall<TData>(
325
+ runtime: CliCommandRuntime,
326
+ route: string,
327
+ call: (connection: AgentDaemonConnection) => Promise<TData>,
328
+ ): Promise<AgentKnowledgeResult<TData>> {
329
+ const connection = resolveDaemonConnection(runtime);
330
+ if (!connection.token) {
331
+ return {
332
+ ok: false,
333
+ kind: 'auth_required',
334
+ error: `No daemon operator token found at ${connection.tokenPath}`,
335
+ baseUrl: connection.baseUrl,
336
+ route,
337
+ };
338
+ }
339
+ try {
340
+ const data = await call(connection);
341
+ return { ok: true, kind: route, route, data };
342
+ } catch (error) {
343
+ return classifyKnowledgeError(error, connection, route);
344
+ }
345
+ }
346
+
347
+ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
348
+ const [sub = 'status', ...rest] = runtime.cli.commandArgs;
349
+ const normalized = sub.toLowerCase();
350
+ const json = runtime.cli.flags.outputFormat === 'json';
351
+
352
+ if (normalized === 'status') {
353
+ const result = await runKnowledgeCall(runtime, 'knowledge.status', async (connection) => (
354
+ await createAgentSdk(connection).knowledge.status()
355
+ ));
356
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
357
+ return {
358
+ output: formatJsonOrText(runtime.cli)(result, formatStatus(result.data)),
359
+ exitCode: 0,
360
+ };
361
+ }
362
+
363
+ if (normalized === 'ask') {
364
+ const query = commandValues(rest).join(' ').trim();
365
+ if (!query) return { output: 'Usage: goodvibes-agent knowledge ask <question> [--limit <n>] [--mode concise|standard|detailed]', exitCode: 2 };
366
+ const mode = readOptionValue(rest, '--mode');
367
+ const selectedMode = mode === 'concise' || mode === 'standard' || mode === 'detailed' ? mode : 'standard';
368
+ const limit = readPositiveInt(rest, '--limit', 8);
369
+ const result = await runKnowledgeCall(runtime, 'knowledge.ask', async (connection) => (
370
+ await createAgentSdk(connection).knowledge.ask({
371
+ query,
372
+ limit,
373
+ mode: selectedMode,
374
+ includeSources: true,
375
+ includeConfidence: true,
376
+ includeLinkedObjects: true,
377
+ })
378
+ ));
379
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
380
+ return {
381
+ output: formatJsonOrText(runtime.cli)(result, formatAsk(result.data, query)),
382
+ exitCode: 0,
383
+ };
384
+ }
385
+
386
+ if (normalized === 'search') {
387
+ const query = commandValues(rest).join(' ').trim();
388
+ if (!query) return { output: 'Usage: goodvibes-agent knowledge search <query> [--limit <n>]', exitCode: 2 };
389
+ const limit = readPositiveInt(rest, '--limit', 10);
390
+ const result = await runKnowledgeCall(runtime, 'knowledge.search', async (connection) => (
391
+ await createAgentSdk(connection).knowledge.search({ query, limit })
392
+ ));
393
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
394
+ return {
395
+ output: formatJsonOrText(runtime.cli)(result, formatSearch(result.data, query)),
396
+ exitCode: 0,
397
+ };
398
+ }
399
+
400
+ if (normalized === 'ingest-url') {
401
+ const values = commandValues(rest);
402
+ const url = values[0];
403
+ if (!url) return { output: 'Usage: goodvibes-agent knowledge ingest-url <url> [--title <title>] [--tags a,b]', exitCode: 2 };
404
+ const title = readOptionValue(rest, '--title');
405
+ const tags = readStringList(rest, '--tags');
406
+ const result = await runKnowledgeCall(runtime, 'knowledge.ingest.url', async (connection) => (
407
+ await createAgentSdk(connection).operator.invoke('knowledge.ingest.url', {
408
+ url,
409
+ title,
410
+ tags,
411
+ sourceType: 'url',
412
+ connectorId: 'goodvibes-agent-cli',
413
+ })
414
+ ));
415
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
416
+ return {
417
+ output: formatJsonOrText(runtime.cli)(result, formatIngest(result.data, url)),
418
+ exitCode: 0,
419
+ };
420
+ }
421
+
422
+ return {
423
+ output: 'Usage: goodvibes-agent knowledge [status|ask <question>|search <query>|ingest-url <url>]',
424
+ exitCode: 2,
425
+ };
426
+ }
427
+
428
+ export async function handleCompatCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
429
+ const connection = resolveDaemonConnection(runtime);
430
+ const metadata = readPackageMetadata();
431
+ const daemon = await fetchDaemonStatus(connection);
432
+ const daemonRecord = isRecord(daemon.body) ? daemon.body : {};
433
+ const daemonVersion = readString(daemonRecord, 'version') ?? 'unknown';
434
+ const versionCompatible = daemon.ok && daemonVersion === metadata.sdkVersion;
435
+ const knowledgeRoute = await runKnowledgeCall(runtime, 'knowledge.status', async (routeConnection) => (
436
+ await createAgentSdk(routeConnection).knowledge.status()
437
+ ));
438
+ const knowledgeRouteReady = knowledgeRoute.ok;
439
+ const value = {
440
+ ok: versionCompatible && knowledgeRouteReady,
441
+ packageVersion: metadata.version,
442
+ sdkPin: metadata.sdkVersion,
443
+ daemon: {
444
+ baseUrl: connection.baseUrl,
445
+ status: daemon.status,
446
+ version: daemonVersion,
447
+ reachable: daemon.ok,
448
+ compatible: versionCompatible,
449
+ },
450
+ auth: {
451
+ tokenPresent: Boolean(connection.token),
452
+ tokenPath: connection.tokenPath,
453
+ },
454
+ agentKnowledge: {
455
+ route: '/api/goodvibes-agent/knowledge/status',
456
+ ready: knowledgeRouteReady,
457
+ kind: knowledgeRoute.ok ? 'ok' : knowledgeRoute.kind,
458
+ },
459
+ };
460
+ const text = [
461
+ 'GoodVibes Agent compatibility',
462
+ ` package: ${metadata.version}`,
463
+ ` SDK pin: ${metadata.sdkVersion}`,
464
+ ` daemon: ${daemonVersion} at ${connection.baseUrl} (${daemon.ok ? 'reachable' : 'unreachable'})`,
465
+ ` version compatible: ${yesNo(versionCompatible)}`,
466
+ ` operator token: ${connection.token ? 'present' : 'missing'} (${connection.tokenPath})`,
467
+ ` Agent knowledge route: ${knowledgeRouteReady ? 'ready' : `not ready (${knowledgeRoute.ok ? 'unknown' : knowledgeRoute.kind})`}`,
468
+ ...(versionCompatible ? [] : [' next: update/restart the external GoodVibes daemon so /status matches the Agent SDK pin.']),
469
+ ].join('\n');
470
+ return {
471
+ output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text,
472
+ exitCode: value.ok ? 0 : 1,
473
+ };
474
+ }
475
+
476
+ export async function handleDelegateCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
477
+ const wrfcRequested = hasFlag(runtime.cli.commandArgs, '--wrfc');
478
+ const task = delegationTaskValues(runtime.cli.commandArgs).join(' ').trim();
479
+ if (!task) {
480
+ return {
481
+ output: 'Usage: goodvibes-agent delegate [--wrfc] <build/fix/review task>',
482
+ exitCode: 2,
483
+ };
484
+ }
485
+ const result = await runKnowledgeCall(runtime, 'sessions.messages.create', async (connection) => {
486
+ const sdk = createAgentSdk(connection);
487
+ const created = await sdk.operator.invoke('sessions.create', {
488
+ title: `Agent delegation: ${task.slice(0, 72)}`,
489
+ surfaceKind: 'goodvibes-agent',
490
+ surfaceId: 'goodvibes-agent-cli',
491
+ });
492
+ const sessionId = isRecord(created.session) && typeof created.session.id === 'string'
493
+ ? created.session.id
494
+ : null;
495
+ if (!sessionId) throw new Error('sessions.create returned no session id.');
496
+ const message = await sdk.operator.invoke('sessions.messages.create', {
497
+ sessionId,
498
+ body: buildDelegationBody(task, wrfcRequested),
499
+ surfaceKind: 'goodvibes-agent',
500
+ surfaceId: 'goodvibes-agent-cli',
501
+ kind: 'task',
502
+ routing: {
503
+ executionIntent: {
504
+ riskClass: 'elevated',
505
+ requiresApproval: true,
506
+ networkPolicy: 'inherit',
507
+ filesystemPolicy: 'workspace-write',
508
+ },
509
+ },
510
+ });
511
+ return { sessionId, message, task, wrfcRequested };
512
+ });
513
+ if (!result.ok) return { output: formatFailure(result, runtime.cli.flags.outputFormat === 'json'), exitCode: 1 };
514
+ const text = [
515
+ 'Delegation submitted to GoodVibes TUI/shared-session routes.',
516
+ ` session: ${result.data.sessionId}`,
517
+ ` mode: ${result.data.wrfcRequested ? 'WRFC requested' : 'direct build delegation'}`,
518
+ ` task: ${result.data.task}`,
519
+ ' next: check GoodVibes TUI shared-session/task status for the result.',
520
+ ].join('\n');
521
+ return {
522
+ output: formatJsonOrText(runtime.cli)(result, text),
523
+ exitCode: 0,
524
+ };
525
+ }
package/src/cli/help.ts CHANGED
@@ -39,6 +39,9 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
39
39
  ' models [provider] List/use/pin selectable models and recent model history',
40
40
  ' providers List/inspect/use provider config/auth posture',
41
41
  ' auth Inspect and manage local users, sessions, and bootstrap auth',
42
+ ' compat Inspect Agent SDK pin, daemon version, and Agent knowledge route readiness',
43
+ ' knowledge Use isolated Agent Knowledge/Wiki routes',
44
+ ' delegate Explicitly delegate build/fix/review work to GoodVibes TUI',
42
45
  ' subscription Start/finish/logout provider subscription sessions',
43
46
  ' secrets List, set, link, delete, and test GoodVibes secret refs',
44
47
  ' sessions List, show, export, or resume saved sessions',
@@ -89,6 +92,10 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
89
92
  ` ${binary} models current`,
90
93
  ` ${binary} models use openai:gpt-5.2`,
91
94
  ` ${binary} providers inspect openai`,
95
+ ` ${binary} compat`,
96
+ ` ${binary} knowledge status`,
97
+ ` ${binary} knowledge ask "What is GoodVibes Agent?"`,
98
+ ` ${binary} delegate --wrfc "fix the failing tests in ~/work/project"`,
92
99
  ` ${binary} surfaces`,
93
100
  ` ${binary} surfaces check`,
94
101
  ` ${binary} service check`,
@@ -147,6 +154,34 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
147
154
  summary: 'Inspect and manage local admin users, bootstrap auth, and local sessions.',
148
155
  examples: ['auth', 'auth add-user admin --password-stdin', 'auth clear-bootstrap'],
149
156
  },
157
+ compat: {
158
+ usage: ['compat', 'compat --json'],
159
+ summary: 'Inspect package SDK pin, live daemon version, and Agent-specific knowledge route readiness.',
160
+ examples: ['compat', 'compat --json'],
161
+ },
162
+ knowledge: {
163
+ usage: [
164
+ 'knowledge status',
165
+ 'knowledge ask <question> [--limit <n>] [--mode concise|standard|detailed]',
166
+ 'knowledge search <query> [--limit <n>]',
167
+ 'knowledge ingest-url <url> [--title <title>] [--tags a,b]',
168
+ ],
169
+ summary: 'Call isolated Agent Knowledge/Wiki routes under /api/goodvibes-agent/knowledge. No default wiki or HomeGraph fallback.',
170
+ examples: [
171
+ 'knowledge status',
172
+ 'knowledge ask "What is GoodVibes Agent?"',
173
+ 'knowledge search "release checklist"',
174
+ 'knowledge ingest-url https://example.com/page --title "Reference"',
175
+ ],
176
+ },
177
+ delegate: {
178
+ usage: ['delegate [--wrfc] <build/fix/review task>'],
179
+ summary: 'Create one shared-session task request for GoodVibes TUI. WRFC is requested only with --wrfc.',
180
+ examples: [
181
+ 'delegate "fix the failing tests in this repo"',
182
+ 'delegate --wrfc "implement the settings screen and review it"',
183
+ ],
184
+ },
150
185
  subscription: {
151
186
  usage: ['subscription list', 'subscription providers', 'subscription inspect <provider>', 'subscription login <provider> start|finish', 'subscription logout <provider>'],
152
187
  summary: 'Manage OAuth/subscription-backed provider sessions such as OpenAI subscription access.',
@@ -21,6 +21,7 @@ import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibe
21
21
  import { inspectProviderAuth } from '@/runtime/index.ts';
22
22
  import { getOrCreateCompanionToken, buildCompanionConnectionInfo, encodeConnectionPayload, formatConnectionBlock } from '@pellux/goodvibes-sdk/platform/pairing';
23
23
  import { generateQrMatrix, renderQrToString } from '@pellux/goodvibes-sdk/platform/pairing';
24
+ import { UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
24
25
  import type { GoodVibesCliParseResult } from './types.ts';
25
26
  import { formatProviderAuthRoute, summarizeProviderAuthRoutes } from './provider-auth-routes.ts';
26
27
  import { classifyProviderSetup } from './provider-classification.ts';
@@ -31,6 +32,7 @@ import { handleServiceCommand } from './service-command.ts';
31
32
  import { handleBundleCommand } from './bundle-command.ts';
32
33
  import { buildListenerTestResult, formatListenerTestResult, handleSurfacesCommand } from './surface-command.ts';
33
34
  import { buildControlPlaneStatusResult, formatControlPlaneStatus, handleSecrets, handleSessions, handleTasks, renderPairing, renderRemote, renderSubscriptions, renderWeb } from './management-commands.ts';
35
+ import { handleAgentKnowledgeCommand, handleCompatCommand, handleDelegateCommand } from './agent-knowledge-command.ts';
34
36
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
35
37
 
36
38
  export interface CliCommandRuntime {
@@ -289,6 +291,14 @@ export function readAuthPaths(runtime: CliCommandRuntime) {
289
291
  };
290
292
  }
291
293
 
294
+ function createCliLocalUserAuthManager(runtime: CliCommandRuntime): UserAuthManager {
295
+ const paths = readAuthPaths(runtime);
296
+ return new UserAuthManager({
297
+ bootstrapFilePath: paths.userStorePath,
298
+ bootstrapCredentialPath: paths.bootstrapCredentialPath,
299
+ });
300
+ }
301
+
292
302
  export async function runNonInteractiveAgent(runtime: CliCommandRuntime): Promise<number> {
293
303
  const prompt = runtime.cli.flags.prompt ?? runtime.cli.positionals.join(' ').trim();
294
304
  if (!prompt) {
@@ -574,7 +584,7 @@ async function renderModels(runtime: CliCommandRuntime): Promise<string> {
574
584
  }
575
585
 
576
586
  async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
577
- return await withRuntimeServices(runtime, (services) => {
587
+ const localUserAuthManager = createCliLocalUserAuthManager(runtime);
578
588
  const [sub = 'status', ...rawRest] = runtime.cli.commandArgs;
579
589
  const rest = commandValues(rawRest);
580
590
  if (sub === 'add-user' || sub === 'add') {
@@ -583,13 +593,13 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
583
593
  const password = readPassword(rawRest);
584
594
  if (!password) return 'Usage: goodvibes auth add-user <username> [--password <value>|--password-stdin] [--role <role>]';
585
595
  const roles = readOptionValues(rawRest, '--role').filter((role) => role.length > 0);
586
- const user = services.localUserAuthManager.addUser(username, password, roles.length > 0 ? roles : ['user']);
596
+ const user = localUserAuthManager.addUser(username, password, roles.length > 0 ? roles : ['user']);
587
597
  return `Auth user added: ${user.username} (${user.roles.join(', ') || 'no roles'})`;
588
598
  }
589
599
  if (sub === 'delete-user' || sub === 'remove-user') {
590
600
  const username = rest[0];
591
601
  if (!username) return 'Usage: goodvibes auth delete-user <username>';
592
- return services.localUserAuthManager.deleteUser(username)
602
+ return localUserAuthManager.deleteUser(username)
593
603
  ? `Auth user deleted: ${username}`
594
604
  : `No auth user found: ${username}`;
595
605
  }
@@ -598,31 +608,31 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
598
608
  if (!username) return 'Usage: goodvibes auth rotate-password <username> [--password <value>|--password-stdin]';
599
609
  const password = readPassword(rawRest);
600
610
  if (!password) return 'Usage: goodvibes auth rotate-password <username> [--password <value>|--password-stdin]';
601
- services.localUserAuthManager.rotatePassword(username, password);
611
+ localUserAuthManager.rotatePassword(username, password);
602
612
  return `Auth password rotated: ${username}`;
603
613
  }
604
614
  if (sub === 'revoke-session') {
605
615
  const token = rest[0];
606
616
  if (!token) return 'Usage: goodvibes auth revoke-session <token-or-fingerprint>';
607
- return services.localUserAuthManager.revokeSession(token)
617
+ return localUserAuthManager.revokeSession(token)
608
618
  ? 'Auth session revoked.'
609
619
  : 'No auth session found.';
610
620
  }
611
621
  if (sub === 'revoke-sessions') {
612
622
  const username = rest[0];
613
623
  if (!username) return 'Usage: goodvibes auth revoke-sessions <username>';
614
- const count = services.localUserAuthManager.revokeSessionsForUser(username);
624
+ const count = localUserAuthManager.revokeSessionsForUser(username);
615
625
  return `Auth sessions revoked for ${username}: ${count}`;
616
626
  }
617
627
  if (sub === 'clear-bootstrap') {
618
- return services.localUserAuthManager.clearBootstrapCredentialFile()
628
+ return localUserAuthManager.clearBootstrapCredentialFile()
619
629
  ? 'Bootstrap credential file removed.'
620
630
  : 'Bootstrap credential file was already absent.';
621
631
  }
622
632
  if (sub !== 'status' && sub !== 'list' && sub !== 'users' && sub !== 'sessions') {
623
633
  return 'Usage: goodvibes auth [status|users|sessions|add-user|delete-user|rotate-password|revoke-session|revoke-sessions|clear-bootstrap]';
624
634
  }
625
- const snapshot = services.localUserAuthManager.inspect();
635
+ const snapshot = localUserAuthManager.inspect();
626
636
  const paths = readAuthPaths(runtime);
627
637
  const value = {
628
638
  ...paths,
@@ -652,7 +662,6 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
652
662
  ` bootstrap credential: ${paths.bootstrapCredentialPresent ? 'present' : 'missing'} (${paths.bootstrapCredentialPath})`,
653
663
  ` operator tokens: ${paths.operatorTokenPresent ? 'present' : 'missing'} (${paths.operatorTokenPath})`,
654
664
  ].join('\n'));
655
- });
656
665
  }
657
666
 
658
667
 
@@ -684,6 +693,21 @@ export async function handleGoodVibesCliCommand(runtime: CliCommandRuntime): Pro
684
693
  console.log(output);
685
694
  return { handled: true, exitCode: exitCodeForText(output) };
686
695
  }
696
+ case 'compat': {
697
+ const result = await handleCompatCommand(runtime);
698
+ console.log(result.output);
699
+ return { handled: true, exitCode: result.exitCode };
700
+ }
701
+ case 'knowledge': {
702
+ const result = await handleAgentKnowledgeCommand(runtime);
703
+ console.log(result.output);
704
+ return { handled: true, exitCode: result.exitCode };
705
+ }
706
+ case 'delegate': {
707
+ const result = await handleDelegateCommand(runtime);
708
+ console.log(result.output);
709
+ return { handled: true, exitCode: result.exitCode };
710
+ }
687
711
  case 'subscription': {
688
712
  const output = await renderSubscriptions(runtime);
689
713
  console.log(output);
package/src/cli/parser.ts CHANGED
@@ -26,6 +26,13 @@ const COMMAND_ALIASES: Readonly<Record<string, GoodVibesCliCommand>> = {
26
26
  providers: 'providers',
27
27
  provider: 'providers',
28
28
  auth: 'auth',
29
+ compat: 'compat',
30
+ compatibility: 'compat',
31
+ knowledge: 'knowledge',
32
+ know: 'knowledge',
33
+ kb: 'knowledge',
34
+ delegate: 'delegate',
35
+ build: 'delegate',
29
36
  subscription: 'subscription',
30
37
  subscriptions: 'subscription',
31
38
  secrets: 'secrets',
package/src/cli/types.ts CHANGED
@@ -10,6 +10,9 @@ export type GoodVibesCliCommand =
10
10
  | 'models'
11
11
  | 'providers'
12
12
  | 'auth'
13
+ | 'compat'
14
+ | 'knowledge'
15
+ | 'delegate'
13
16
  | 'subscription'
14
17
  | 'secrets'
15
18
  | 'sessions'
@@ -203,7 +203,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
203
203
  detail: 'Agent does not become the coding TUI. Build, implement, fix, patch, and review work must be handed to GoodVibes TUI with the full original ask and WRFC only when explicitly requested.',
204
204
  actions: [
205
205
  { id: 'delegate-guidance', label: 'Delegation rule', detail: 'For build/fix/review work, delegate one request to GoodVibes TUI instead of spawning local Engineer/Reviewer/Tester roots.', kind: 'guidance', safety: 'delegates' },
206
- { id: 'review-command', label: 'Review delegation command', detail: 'Use /review or /wrfc only when the user explicitly asks for code review/build execution.', command: '/review', kind: 'command', safety: 'delegates' },
206
+ { id: 'review-command', label: 'Review delegation command', detail: 'Use /delegate --wrfc only when the user explicitly asks for code review/build execution.', command: '/delegate --wrfc <task>', kind: 'command', safety: 'delegates' },
207
207
  { id: 'remote-policy', label: 'Remote runner policy', detail: 'Remote dispatch/rerun is blocked in Agent; TUI owns runner topology for delegated build work.', command: '/remote dispatch', kind: 'command', safety: 'blocked' },
208
208
  ],
209
209
  },
@@ -0,0 +1,129 @@
1
+ import type { CommandContext, CommandRegistry } from '../command-registry.ts';
2
+ import type { SharedSessionParticipant } from '@pellux/goodvibes-sdk/platform/control-plane';
3
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
4
+
5
+ function hasFlag(args: readonly string[], flag: string): boolean {
6
+ return args.includes(flag);
7
+ }
8
+
9
+ function delegationTaskValues(args: readonly string[]): string[] {
10
+ const values: string[] = [];
11
+ for (let index = 0; index < args.length; index += 1) {
12
+ const token = args[index]!;
13
+ if (token === '--wrfc') continue;
14
+ if (!token.startsWith('--')) {
15
+ values.push(token);
16
+ continue;
17
+ }
18
+ }
19
+ return values;
20
+ }
21
+
22
+ function buildDelegationBody(task: string, wrfcRequested: boolean): string {
23
+ return [
24
+ 'GoodVibes Agent explicit build delegation.',
25
+ '',
26
+ 'Original user ask:',
27
+ task,
28
+ '',
29
+ 'Agent policy:',
30
+ '- GoodVibes Agent is not the coding TUI.',
31
+ '- Preserve the full original ask.',
32
+ '- GoodVibes TUI owns file edits, git/worktree flows, sandbox/QEMU UX, and any WRFC owner chain.',
33
+ wrfcRequested
34
+ ? '- WRFC was explicitly requested by the Agent user for this build/fix/review delegation.'
35
+ : '- WRFC was not explicitly requested; do not turn this into WRFC solely because it came from Agent.',
36
+ ].join('\n');
37
+ }
38
+
39
+ export function registerDelegationRuntimeCommands(registry: CommandRegistry): void {
40
+ const makeHandler = (defaultWrfc: boolean) => async (args: string[], ctx: CommandContext): Promise<void> => {
41
+ const wrfcRequested = defaultWrfc || hasFlag(args, '--wrfc');
42
+ const task = delegationTaskValues(args).join(' ').trim();
43
+ if (!task) {
44
+ ctx.print(defaultWrfc ? 'Usage: /wrfc <build/fix/review task>' : 'Usage: /delegate [--wrfc] <build/fix/review task>');
45
+ return;
46
+ }
47
+ const operator = ctx.clients?.operator;
48
+ if (!operator) {
49
+ ctx.print([
50
+ 'Delegation unavailable: no operator client is attached.',
51
+ 'Use the external daemon/shared-session route from a configured Agent runtime, or open GoodVibes TUI in the target workspace.',
52
+ ].join('\n'));
53
+ return;
54
+ }
55
+ try {
56
+ const participant = {
57
+ surfaceKind: 'service',
58
+ surfaceId: 'goodvibes-agent',
59
+ externalId: ctx.session.runtime.sessionId,
60
+ displayName: 'GoodVibes Agent',
61
+ lastSeenAt: Date.now(),
62
+ } satisfies SharedSessionParticipant;
63
+ const session = await operator.sessions.ensureSession({
64
+ title: `Agent delegation: ${task.slice(0, 72)}`,
65
+ participant,
66
+ metadata: {
67
+ originSurface: 'goodvibes-agent',
68
+ sourceSessionId: ctx.session.runtime.sessionId,
69
+ task,
70
+ wrfcRequested,
71
+ },
72
+ });
73
+ await operator.sessions.submitMessage({
74
+ sessionId: session.id,
75
+ body: buildDelegationBody(task, wrfcRequested),
76
+ surfaceKind: participant.surfaceKind,
77
+ surfaceId: participant.surfaceId,
78
+ externalId: participant.externalId,
79
+ displayName: participant.displayName,
80
+ title: `Agent delegation: ${task.slice(0, 72)}`,
81
+ metadata: {
82
+ originSurface: 'goodvibes-agent',
83
+ sourceSessionId: ctx.session.runtime.sessionId,
84
+ kind: 'task',
85
+ task,
86
+ wrfcRequested,
87
+ },
88
+ routing: {
89
+ executionIntent: {
90
+ riskClass: 'elevated',
91
+ requiresApproval: true,
92
+ networkPolicy: 'inherit',
93
+ filesystemPolicy: 'workspace-write',
94
+ },
95
+ },
96
+ });
97
+ ctx.print([
98
+ 'Delegation submitted to GoodVibes TUI/shared-session routes.',
99
+ ` session: ${session.id}`,
100
+ ` mode: ${wrfcRequested ? 'WRFC requested' : 'direct build delegation'}`,
101
+ ` task: ${task}`,
102
+ ' next: check GoodVibes TUI shared-session/task status for the result.',
103
+ ].join('\n'));
104
+ } catch (error) {
105
+ ctx.print([
106
+ 'Delegation failed.',
107
+ ` error: ${summarizeError(error)}`,
108
+ ' fallback: open GoodVibes TUI in the target workspace and paste the original task there.',
109
+ ].join('\n'));
110
+ }
111
+ };
112
+
113
+ registry.register({
114
+ name: 'delegate',
115
+ aliases: ['build'],
116
+ description: 'Explicitly delegate build/fix/review work to GoodVibes TUI through shared-session routes',
117
+ usage: '[--wrfc] <task>',
118
+ argsHint: '[--wrfc] <task>',
119
+ handler: makeHandler(false),
120
+ });
121
+ registry.register({
122
+ name: 'wrfc',
123
+ aliases: ['review'],
124
+ description: 'Explicitly delegate build/fix/review work to GoodVibes TUI with WRFC requested',
125
+ usage: '<task>',
126
+ argsHint: '<task>',
127
+ handler: makeHandler(true),
128
+ });
129
+ }
@@ -2,7 +2,6 @@ import type { CommandRegistry } from '../command-registry.ts';
2
2
  import type { SelectionItem } from '../selection-modal.ts';
3
3
  import { EFFORT_DESCRIPTIONS } from '@pellux/goodvibes-sdk/platform/providers';
4
4
  import { REASONING_BUDGET_MAP } from '@pellux/goodvibes-sdk/platform/providers';
5
- import { executeWriteQuit } from './quit-shared.ts';
6
5
  import { compactConversation, requireKeybindingsManager, requireProviderApi } from './runtime-services.ts';
7
6
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
8
7
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
@@ -159,7 +158,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
159
158
  { id: '/secrets', label: '/secrets set|link|get|test|list|delete', detail: 'Manage encrypted and provider-backed secrets', category: 'Tools & System' },
160
159
  { id: '/help', label: '/help', detail: 'This help', category: 'Tools & System' },
161
160
  { id: '/quit', label: '/quit', detail: 'Exit', category: 'Tools & System' },
162
- { id: '/wq', label: '/wq', detail: 'Commit all git changes and then exit', category: 'Tools & System' },
161
+ { id: '/wq', label: '/wq', detail: 'Blocked in Agent; git commit/exit belongs to GoodVibes TUI', category: 'Tools & System' },
163
162
  ];
164
163
  ctx.openSelection('Help — Commands', items, { allowSearch: true }, (result) => {
165
164
  if (!result) return;
@@ -173,7 +172,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
173
172
  });
174
173
  return;
175
174
  }
176
- ctx.print('Use /help to open the help modal. Commands: /agent, /model, /provider, /config, /template, /tools, /paste, /sessions, /bookmarks, /save, /load, /undo, /redo, /retry, /clear, /reset, /compact, /export, /title, /effort, /expand, /collapse, /debug, /quit, /wq');
175
+ ctx.print('Use /help to open the help modal. Commands: /agent, /model, /provider, /config, /template, /tools, /paste, /sessions, /bookmarks, /save, /load, /undo, /redo, /retry, /clear, /reset, /compact, /export, /title, /effort, /expand, /collapse, /debug, /quit');
177
176
  },
178
177
  });
179
178
 
@@ -225,9 +224,13 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
225
224
  registry.register({
226
225
  name: 'wq',
227
226
  aliases: [':wq'],
228
- description: 'Commit all git changes and then exit',
229
- async handler(_args, ctx) {
230
- await executeWriteQuit(ctx);
227
+ description: 'Blocked in Agent; git commit/exit is owned by GoodVibes TUI',
228
+ handler(_args, ctx) {
229
+ ctx.print([
230
+ 'Blocked: /wq is not available in GoodVibes Agent.',
231
+ 'Git commit, worktree, and coding-session exit flows belong to GoodVibes TUI.',
232
+ 'No files, commits, or repository state were changed.',
233
+ ].join('\n'));
231
234
  },
232
235
  });
233
236
 
@@ -54,6 +54,7 @@ import { registerCloudflareRuntimeCommands } from './commands/cloudflare-runtime
54
54
  import { registerWorkPlanRuntimeCommands } from './commands/work-plan-runtime.ts';
55
55
  import { registerAgentWorkspaceRuntimeCommands } from './commands/agent-workspace-runtime.ts';
56
56
  import { registerAgentExternalizedTuiCommands } from './commands/agent-externalized-tui.ts';
57
+ import { registerDelegationRuntimeCommands } from './commands/delegation-runtime.ts';
57
58
 
58
59
  /**
59
60
  * registerBuiltinCommands - Register all built-in slash commands into the registry.
@@ -62,6 +63,7 @@ import { registerAgentExternalizedTuiCommands } from './commands/agent-externali
62
63
  export function registerBuiltinCommands(registry: CommandRegistry): void {
63
64
  registerShellCoreCommands(registry);
64
65
  registerAgentWorkspaceRuntimeCommands(registry);
66
+ registerDelegationRuntimeCommands(registry);
65
67
  registerConfigCommand(registry);
66
68
  registerOperatorRuntimeCommands(registry);
67
69
  registerIntegrationRuntimeCommands(registry);
@@ -8,7 +8,7 @@ export interface SubmissionRouterInput {
8
8
  }
9
9
 
10
10
  const PLAN_COMMANDS = new Set(['plan']);
11
- const DELEGATION_COMMANDS = new Set(['review', 'wrfc', 'teamwork', 'agents', 'remote']);
11
+ const DELEGATION_COMMANDS = new Set(['delegate', 'build', 'review', 'wrfc', 'teamwork', 'agents', 'remote']);
12
12
  const PANEL_COMMANDS = new Set(['panel']);
13
13
  const ORCHESTRATION_COMMANDS = new Set([
14
14
  'orchestration',
@@ -3,7 +3,6 @@ import { AgentLogsPanel } from '../agent-logs-panel.ts';
3
3
  import { ContextVisualizerPanel } from '../context-visualizer-panel.ts';
4
4
  import { ThinkingPanel } from '../thinking-panel.ts';
5
5
  import { ToolInspectorPanel } from '../tool-inspector-panel.ts';
6
- import { WrfcPanel } from '../wrfc-panel.ts';
7
6
  import { SchedulePanel } from '../schedule-panel.ts';
8
7
  import { ProjectPlanningPanel } from '../project-planning-panel.ts';
9
8
  import { WorkPlanPanel } from '../work-plan-panel.ts';
@@ -67,19 +66,6 @@ export function registerAgentPanels(manager: PanelManager, deps: ResolvedBuiltin
67
66
  },
68
67
  });
69
68
 
70
- manager.registerType({
71
- id: 'wrfc',
72
- name: 'WRFC',
73
- icon: 'W',
74
- category: 'agent',
75
- description: 'WRFC chain view: write, review, fix, and confirm cycle status',
76
- preload: true,
77
- factory: () => {
78
- const ui = requireUiServices(deps);
79
- return new WrfcPanel(ui.events.workflows, { controller: ui.agents.wrfcController });
80
- },
81
- });
82
-
83
69
  manager.registerType({
84
70
  id: 'work-plan',
85
71
  name: 'Work Plan',
@@ -33,6 +33,7 @@ import type { TaskManager } from '@/runtime/index.ts';
33
33
  import type { UiRuntimeServices } from './ui-services.ts';
34
34
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
35
35
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
36
+ import { createKnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
36
37
 
37
38
  export interface BootstrapShellState {
38
39
  readonly commandRegistry: CommandRegistry;
@@ -208,7 +209,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
208
209
  memoryRegistry: services.memoryRegistry,
209
210
  integrationHelpers: services.integrationHelpers,
210
211
  automationManager: services.automationManager,
211
- knowledgeService: services.knowledgeService,
212
+ knowledgeService: services.agentKnowledgeService,
212
213
  projectPlanningService: services.projectPlanningService,
213
214
  projectPlanningProjectId: services.projectPlanningProjectId,
214
215
  workPlanStore: services.workPlanStore,
@@ -238,7 +239,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
238
239
  sessionOrchestration: services.sessionOrchestration,
239
240
  operatorClient: directTransport.operator,
240
241
  peerClient: directTransport.peer,
241
- knowledgeApi,
242
+ knowledgeApi: createKnowledgeApi(services.agentKnowledgeService, { memoryRegistry: services.memoryRegistry }),
242
243
  hookApi,
243
244
  mcpApi,
244
245
  opsApi,
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.0';
9
+ let _version = '0.1.2';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;