@pellux/goodvibes-agent 0.1.83 → 0.1.84

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,15 @@
2
2
 
3
3
  All notable changes to GoodVibes Agent will be recorded here.
4
4
 
5
+ ## 0.1.84 - 2026-06-01
6
+
7
+ - 35dbf3d Show live external runtime status
8
+
9
+ ## Unreleased
10
+
11
+ - Foregrounded live external-runtime reachability, SDK compatibility, operator-token state, and isolated Agent Knowledge readiness in `status`/`doctor` output.
12
+ - Removed the deprecated `actions/cache@v4` setup step so future CI/CD runs avoid Node 20 deprecation annotations from the shared workflow setup action.
13
+
5
14
  ## 0.1.83 - 2026-06-01
6
15
 
7
16
  - f5099c1 Hide runtime-owned settings from Agent workspace
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.83",
3
+ "version": "0.1.84",
4
4
  "private": false,
5
5
  "description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
6
6
  "type": "module",
@@ -22,6 +22,7 @@ import {
22
22
  renderOnboardingCliStatus,
23
23
  } from './index.ts';
24
24
  import { buildCliServicePosture } from './service-posture.ts';
25
+ import { inspectCliExternalRuntime } from './external-runtime.ts';
25
26
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
26
27
  import { resolveAgentRuntimeProfileHome } from '../agent/runtime-profile.ts';
27
28
 
@@ -147,6 +148,10 @@ export async function prepareShellCliRuntime(
147
148
  workingDirectory: bootstrapWorkingDir,
148
149
  homeDirectory: bootstrapHomeDirectory,
149
150
  });
151
+ const externalRuntime = await inspectCliExternalRuntime({
152
+ configManager,
153
+ homeDirectory: bootstrapHomeDirectory,
154
+ });
150
155
  const statusOptions = {
151
156
  configManager,
152
157
  workingDirectory: bootstrapWorkingDir,
@@ -161,6 +166,7 @@ export async function prepareShellCliRuntime(
161
166
  operatorTokenPresent: existsSync(operatorTokenPath),
162
167
  },
163
168
  service,
169
+ externalRuntime,
164
170
  doctor: cli.command === 'doctor',
165
171
  outputFormat: cli.flags.outputFormat,
166
172
  };
@@ -0,0 +1,195 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
4
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
5
+ import { SDK_VERSION } from '../version.ts';
6
+
7
+ type JsonRecord = Record<string, unknown>;
8
+
9
+ export interface CliExternalRuntimeInspectionOptions {
10
+ readonly configManager: Pick<ConfigManager, 'get'>;
11
+ readonly homeDirectory: string;
12
+ readonly timeoutMs?: number;
13
+ }
14
+
15
+ export interface CliExternalRuntimeSnapshot {
16
+ readonly baseUrl: string;
17
+ readonly statusCode: number | null;
18
+ readonly reachable: boolean;
19
+ readonly version: string;
20
+ readonly expectedVersion: string;
21
+ readonly compatible: boolean;
22
+ readonly operatorToken: {
23
+ readonly present: boolean;
24
+ readonly path: string;
25
+ };
26
+ readonly agentKnowledge: {
27
+ readonly route: '/api/goodvibes-agent/knowledge/status';
28
+ readonly ready: boolean;
29
+ readonly kind: 'ok' | 'auth_required' | 'daemon_unavailable' | 'version_mismatch' | 'route_unavailable' | 'runtime_error';
30
+ readonly statusCode: number | null;
31
+ };
32
+ readonly error: string | null;
33
+ }
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 resolveBaseUrl(configManager: Pick<ConfigManager, 'get'>): string {
45
+ const host = String(configManager.get('controlPlane.host') ?? '127.0.0.1');
46
+ const port = Number(configManager.get('controlPlane.port') ?? 3421);
47
+ return `http://${host}:${Number.isFinite(port) ? port : 3421}`;
48
+ }
49
+
50
+ function readOperatorToken(homeDirectory: string): { readonly token: string | null; readonly path: string } {
51
+ const path = join(homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
52
+ if (!existsSync(path)) return { token: null, path };
53
+ try {
54
+ const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown;
55
+ const token = isRecord(parsed) && typeof parsed.token === 'string' ? parsed.token : null;
56
+ return { token, path };
57
+ } catch {
58
+ return { token: null, path };
59
+ }
60
+ }
61
+
62
+ async function fetchJson(
63
+ url: string,
64
+ token: string | null,
65
+ timeoutMs: number,
66
+ ): Promise<{ readonly ok: boolean; readonly status: number; readonly body: unknown }> {
67
+ const controller = new AbortController();
68
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
69
+ try {
70
+ const response = await fetch(url, {
71
+ headers: token ? { authorization: `Bearer ${token}` } : undefined,
72
+ signal: controller.signal,
73
+ });
74
+ const text = await response.text();
75
+ let body: unknown = text;
76
+ if (text.trim().length > 0) {
77
+ try {
78
+ body = JSON.parse(text) as unknown;
79
+ } catch {
80
+ body = text;
81
+ }
82
+ }
83
+ return { ok: response.ok, status: response.status, body };
84
+ } finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }
88
+
89
+ export async function inspectCliExternalRuntime(
90
+ options: CliExternalRuntimeInspectionOptions,
91
+ ): Promise<CliExternalRuntimeSnapshot> {
92
+ const baseUrl = resolveBaseUrl(options.configManager);
93
+ const token = readOperatorToken(options.homeDirectory);
94
+ const timeoutMs = options.timeoutMs ?? 1500;
95
+ const route = '/api/goodvibes-agent/knowledge/status' as const;
96
+
97
+ try {
98
+ const status = await fetchJson(`${baseUrl}/status`, token.token, timeoutMs);
99
+ const statusRecord = isRecord(status.body) ? status.body : {};
100
+ const version = readString(statusRecord, 'version') ?? 'unknown';
101
+ const compatible = status.ok && version === SDK_VERSION;
102
+
103
+ if (!status.ok) {
104
+ return {
105
+ baseUrl,
106
+ statusCode: status.status,
107
+ reachable: false,
108
+ version,
109
+ expectedVersion: SDK_VERSION,
110
+ compatible: false,
111
+ operatorToken: { present: Boolean(token.token), path: token.path },
112
+ agentKnowledge: {
113
+ route,
114
+ ready: false,
115
+ kind: status.status === 401 ? 'auth_required' : 'daemon_unavailable',
116
+ statusCode: status.status,
117
+ },
118
+ error: typeof status.body === 'string' ? status.body : `HTTP ${status.status}`,
119
+ };
120
+ }
121
+
122
+ if (!token.token) {
123
+ return {
124
+ baseUrl,
125
+ statusCode: status.status,
126
+ reachable: true,
127
+ version,
128
+ expectedVersion: SDK_VERSION,
129
+ compatible,
130
+ operatorToken: { present: false, path: token.path },
131
+ agentKnowledge: {
132
+ route,
133
+ ready: false,
134
+ kind: 'auth_required',
135
+ statusCode: null,
136
+ },
137
+ error: null,
138
+ };
139
+ }
140
+
141
+ if (!compatible) {
142
+ return {
143
+ baseUrl,
144
+ statusCode: status.status,
145
+ reachable: true,
146
+ version,
147
+ expectedVersion: SDK_VERSION,
148
+ compatible: false,
149
+ operatorToken: { present: true, path: token.path },
150
+ agentKnowledge: {
151
+ route,
152
+ ready: false,
153
+ kind: 'version_mismatch',
154
+ statusCode: null,
155
+ },
156
+ error: null,
157
+ };
158
+ }
159
+
160
+ const knowledge = await fetchJson(`${baseUrl}${route}`, token.token, timeoutMs);
161
+ return {
162
+ baseUrl,
163
+ statusCode: status.status,
164
+ reachable: true,
165
+ version,
166
+ expectedVersion: SDK_VERSION,
167
+ compatible,
168
+ operatorToken: { present: true, path: token.path },
169
+ agentKnowledge: {
170
+ route,
171
+ ready: knowledge.ok,
172
+ kind: knowledge.ok ? 'ok' : knowledge.status === 401 ? 'auth_required' : knowledge.status === 404 ? 'route_unavailable' : 'runtime_error',
173
+ statusCode: knowledge.status,
174
+ },
175
+ error: knowledge.ok ? null : typeof knowledge.body === 'string' ? knowledge.body : `HTTP ${knowledge.status}`,
176
+ };
177
+ } catch (error) {
178
+ return {
179
+ baseUrl,
180
+ statusCode: null,
181
+ reachable: false,
182
+ version: 'unknown',
183
+ expectedVersion: SDK_VERSION,
184
+ compatible: false,
185
+ operatorToken: { present: Boolean(token.token), path: token.path },
186
+ agentKnowledge: {
187
+ route,
188
+ ready: false,
189
+ kind: 'daemon_unavailable',
190
+ statusCode: null,
191
+ },
192
+ error: summarizeError(error),
193
+ };
194
+ }
195
+ }
package/src/cli/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from './types.ts';
2
2
  export * from './parser.ts';
3
3
  export * from './help.ts';
4
4
  export * from './status.ts';
5
+ export * from './external-runtime.ts';
5
6
  export * from './completion.ts';
6
7
  export * from './config-overrides.ts';
7
8
  export * from './endpoints.ts';
package/src/cli/status.ts CHANGED
@@ -4,6 +4,7 @@ import { resolveRuntimeEndpointBinding } from './endpoints.ts';
4
4
  import { isNetworkFacing } from './network-posture.ts';
5
5
  import type { GoodVibesCliOutputFormat } from './types.ts';
6
6
  import type { CliServicePosture } from './service-posture.ts';
7
+ import type { CliExternalRuntimeSnapshot } from './external-runtime.ts';
7
8
  import { getProviderIdFromModel } from '../config/provider-model.ts';
8
9
 
9
10
  export interface CliStatusOptions {
@@ -13,6 +14,7 @@ export interface CliStatusOptions {
13
14
  readonly onboardingMarkers?: OnboardingCheckMarkersState;
14
15
  readonly auth?: CliAuthStatus;
15
16
  readonly service?: CliServicePosture;
17
+ readonly externalRuntime?: CliExternalRuntimeSnapshot;
16
18
  readonly doctor?: boolean;
17
19
  readonly outputFormat?: GoodVibesCliOutputFormat;
18
20
  }
@@ -58,6 +60,7 @@ export interface CliStatusSnapshot {
58
60
  readonly restartOnFailure: unknown;
59
61
  readonly lifecycle?: CliServicePosture;
60
62
  };
63
+ readonly externalRuntime: CliExternalRuntimeSnapshot | null;
61
64
  readonly runtimeEndpoints: {
62
65
  readonly controlPlane: ReturnType<typeof resolveRuntimeEndpointBinding> & { readonly enabled: unknown };
63
66
  readonly httpListener: ReturnType<typeof resolveRuntimeEndpointBinding> & { readonly enabled: unknown };
@@ -117,6 +120,54 @@ export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliD
117
120
 
118
121
  const findings: CliDoctorFinding[] = [];
119
122
 
123
+ if (options.externalRuntime) {
124
+ if (!options.externalRuntime.reachable) {
125
+ findings.push({
126
+ id: 'external-runtime-unreachable',
127
+ area: 'runtime',
128
+ severity: 'warning',
129
+ summary: 'External GoodVibes runtime is not reachable.',
130
+ cause: `Agent could not reach ${options.externalRuntime.baseUrl}${options.externalRuntime.error ? `: ${options.externalRuntime.error}` : '.'}`,
131
+ impact: 'Companion chat, isolated Agent Knowledge, approvals, automation status, and build delegation cannot work until the external runtime is available.',
132
+ action: 'Start or repair the runtime-owning GoodVibes TUI/host, then rerun goodvibes-agent status.',
133
+ });
134
+ } else if (!options.externalRuntime.compatible) {
135
+ findings.push({
136
+ id: 'external-runtime-version-mismatch',
137
+ area: 'runtime',
138
+ severity: 'warning',
139
+ summary: 'External GoodVibes runtime SDK version does not match Agent.',
140
+ cause: `Runtime reports SDK ${options.externalRuntime.version}; Agent expects ${options.externalRuntime.expectedVersion}.`,
141
+ impact: 'Agent-only routes, especially isolated Agent Knowledge, may be missing or incompatible.',
142
+ action: 'Update/restart the runtime-owning GoodVibes TUI/host so /status matches this Agent package SDK pin.',
143
+ });
144
+ }
145
+
146
+ if (!options.externalRuntime.operatorToken.present) {
147
+ findings.push({
148
+ id: 'external-runtime-token-missing',
149
+ area: 'auth',
150
+ severity: 'warning',
151
+ summary: 'External runtime operator token is missing.',
152
+ cause: `No operator token was found at ${options.externalRuntime.operatorToken.path}.`,
153
+ impact: 'Agent can inspect only unauthenticated routes and cannot use protected runtime APIs.',
154
+ action: 'Pair or provision access through the runtime-owning GoodVibes TUI/host, then rerun goodvibes-agent auth.',
155
+ });
156
+ }
157
+
158
+ if (options.externalRuntime.reachable && options.externalRuntime.operatorToken.present && !options.externalRuntime.agentKnowledge.ready) {
159
+ findings.push({
160
+ id: 'agent-knowledge-route-not-ready',
161
+ area: 'runtime',
162
+ severity: 'warning',
163
+ summary: 'Isolated Agent Knowledge route is not ready.',
164
+ cause: `${options.externalRuntime.agentKnowledge.route} returned ${options.externalRuntime.agentKnowledge.kind}${options.externalRuntime.agentKnowledge.statusCode === null ? '' : ` (${options.externalRuntime.agentKnowledge.statusCode})`}.`,
165
+ impact: 'Agent Knowledge ask/search will not use any fallback wiki or HomeGraph segment; it will fail closed until the Agent route is available.',
166
+ action: 'Update/restart the external runtime to the Agent-compatible SDK and verify goodvibes-agent compat.',
167
+ });
168
+ }
169
+ }
170
+
120
171
  if (serverBackedEnabled && !serviceEnabled) {
121
172
  findings.push({
122
173
  id: 'runtime-ownership-external',
@@ -272,6 +323,7 @@ export function buildCliStatusSnapshot(options: CliStatusOptions): CliStatusSnap
272
323
  restartOnFailure: config.get('service.restartOnFailure'),
273
324
  ...(options.service ? { lifecycle: options.service } : {}),
274
325
  },
326
+ externalRuntime: options.externalRuntime ?? null,
275
327
  runtimeEndpoints: {
276
328
  controlPlane: { enabled: config.get('controlPlane.enabled'), ...controlPlaneBinding },
277
329
  httpListener: { enabled: config.get('danger.httpListener'), ...httpListenerBinding },
@@ -300,6 +352,7 @@ export function renderCliStatus(options: CliStatusOptions): string {
300
352
  const webBinding = snapshot.runtimeEndpoints.web;
301
353
  const marker = options.onboardingMarkers?.effective;
302
354
  const findings = snapshot.findings;
355
+ const externalRuntime = snapshot.externalRuntime;
303
356
 
304
357
  if (options.outputFormat === 'json') return JSON.stringify(snapshot, null, 2);
305
358
 
@@ -326,7 +379,21 @@ export function renderCliStatus(options: CliStatusOptions): string {
326
379
  ? ` operatorTokens: ${options.auth.operatorTokenPresent ? 'present' : 'missing'} (${options.auth.operatorTokenPath})`
327
380
  : ' operatorTokens: unknown',
328
381
  '',
329
- 'External Runtime Connection:',
382
+ 'External Runtime:',
383
+ ...(externalRuntime ? [
384
+ ` baseUrl: ${externalRuntime.baseUrl}`,
385
+ ` reachable: ${yesNo(externalRuntime.reachable)}${externalRuntime.statusCode === null ? '' : ` (HTTP ${externalRuntime.statusCode})`}`,
386
+ ` sdk: ${externalRuntime.version} expected ${externalRuntime.expectedVersion}`,
387
+ ` compatible: ${yesNo(externalRuntime.compatible)}`,
388
+ ` operatorToken: ${externalRuntime.operatorToken.present ? 'present' : 'missing'} (${externalRuntime.operatorToken.path})`,
389
+ ` Agent Knowledge: ${externalRuntime.agentKnowledge.ready ? 'ready' : `not ready (${externalRuntime.agentKnowledge.kind})`}`,
390
+ ...(externalRuntime.error ? [` error: ${externalRuntime.error}`] : []),
391
+ ] : [
392
+ ' live check: unavailable',
393
+ ]),
394
+ '',
395
+ 'External Runtime Ownership:',
396
+ ` owner: external GoodVibes runtime host`,
330
397
  ` hostConfigEnabled: ${yesNo(serviceEnabled)}`,
331
398
  ` hostAutostart: ${yesNo(serviceAutostart)}`,
332
399
  ` hostRestartOnFailure: ${yesNo(restartOnFailure)}`,
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.83';
9
+ let _version = '0.1.84';
10
10
  let _sdkVersion = '0.33.35';
11
11
  try {
12
12
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {