@pellux/goodvibes-agent 1.0.31 → 1.0.34

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +59 -2
  2. package/README.md +72 -58
  3. package/dist/package/main.js +7572 -2430
  4. package/docs/README.md +25 -21
  5. package/docs/channels-remote-and-api.md +6 -2
  6. package/docs/connected-host.md +27 -6
  7. package/docs/getting-started.md +87 -66
  8. package/docs/knowledge-artifacts-and-multimodal.md +16 -4
  9. package/docs/project-planning.md +2 -2
  10. package/docs/providers-and-routing.md +3 -2
  11. package/docs/release-and-publishing.md +15 -11
  12. package/docs/tools-and-commands.md +150 -128
  13. package/docs/voice-and-live-tts.md +1 -1
  14. package/package.json +8 -3
  15. package/release/live-verification/live-verification.json +148 -0
  16. package/release/live-verification/live-verification.md +187 -0
  17. package/release/performance-snapshot.json +57 -0
  18. package/release/release-notes.md +19 -0
  19. package/release/release-readiness.json +581 -0
  20. package/src/agent/harness-control.ts +42 -3
  21. package/src/cli/agent-knowledge-command.ts +5 -5
  22. package/src/cli/agent-knowledge-format.ts +11 -0
  23. package/src/cli/agent-knowledge-runtime.ts +92 -13
  24. package/src/cli/bundle-command.ts +5 -4
  25. package/src/cli/entrypoint.ts +5 -2
  26. package/src/cli/external-runtime.ts +2 -15
  27. package/src/cli/management.ts +4 -3
  28. package/src/input/commands/guidance-runtime.ts +1 -1
  29. package/src/input/commands/knowledge.ts +2 -2
  30. package/src/runtime/bootstrap.ts +10 -18
  31. package/src/runtime/connected-host-auth.ts +16 -0
  32. package/src/tools/agent-analysis-registry-policy.ts +2 -9
  33. package/src/tools/agent-channel-send-tool.ts +3 -9
  34. package/src/tools/agent-context-policy.ts +1 -5
  35. package/src/tools/agent-find-policy.ts +1 -4
  36. package/src/tools/agent-harness-channel-metadata.ts +177 -0
  37. package/src/tools/agent-harness-cli-metadata.ts +4 -1
  38. package/src/tools/agent-harness-command-catalog.ts +10 -3
  39. package/src/tools/agent-harness-connected-host-status.ts +9 -3
  40. package/src/tools/agent-harness-delegation-posture.ts +216 -0
  41. package/src/tools/agent-harness-keybinding-metadata.ts +57 -22
  42. package/src/tools/agent-harness-mcp-metadata.ts +248 -0
  43. package/src/tools/agent-harness-media-posture.ts +282 -0
  44. package/src/tools/agent-harness-metadata.ts +44 -9
  45. package/src/tools/agent-harness-model-routing.ts +501 -0
  46. package/src/tools/agent-harness-model-tool-catalog.ts +7 -2
  47. package/src/tools/agent-harness-notification-metadata.ts +217 -0
  48. package/src/tools/agent-harness-operator-methods.ts +285 -0
  49. package/src/tools/agent-harness-pairing-posture.ts +265 -0
  50. package/src/tools/agent-harness-panel-metadata.ts +26 -12
  51. package/src/tools/agent-harness-provider-account-metadata.ts +205 -0
  52. package/src/tools/agent-harness-release-evidence.ts +364 -0
  53. package/src/tools/agent-harness-release-readiness.ts +298 -0
  54. package/src/tools/agent-harness-security-posture.ts +648 -0
  55. package/src/tools/agent-harness-service-posture.ts +207 -0
  56. package/src/tools/agent-harness-session-metadata.ts +284 -0
  57. package/src/tools/agent-harness-setup-posture.ts +295 -0
  58. package/src/tools/agent-harness-tool-schema.ts +104 -27
  59. package/src/tools/agent-harness-tool.ts +251 -235
  60. package/src/tools/agent-harness-ui-surface-metadata.ts +20 -12
  61. package/src/tools/agent-harness-workspace-actions.ts +260 -0
  62. package/src/tools/agent-knowledge-ingest-tool.ts +4 -10
  63. package/src/tools/agent-knowledge-tool.ts +120 -25
  64. package/src/tools/agent-local-registry-tool.ts +3 -7
  65. package/src/tools/agent-media-generate-tool.ts +2 -8
  66. package/src/tools/agent-notify-tool.ts +3 -8
  67. package/src/tools/agent-operator-action-tool.ts +4 -10
  68. package/src/tools/agent-operator-briefing-tool.ts +1 -6
  69. package/src/tools/agent-read-policy.ts +1 -4
  70. package/src/tools/agent-reminder-schedule-tool.ts +4 -9
  71. package/src/tools/agent-tool-policy-guard.ts +15 -51
  72. package/src/tools/agent-web-search-policy.ts +1 -4
  73. package/src/tools/agent-work-plan-tool.ts +1 -6
  74. package/src/version.ts +2 -2
@@ -77,6 +77,18 @@ export interface HarnessSettingDescriptor {
77
77
  readonly lookup?: HarnessSettingLookup;
78
78
  }
79
79
 
80
+ export interface HarnessSettingSummary {
81
+ readonly key: string;
82
+ readonly category: string;
83
+ readonly type: ConfigSetting['type'];
84
+ readonly value: unknown;
85
+ readonly configured: boolean;
86
+ readonly writable: boolean;
87
+ readonly visibleInWorkspace: boolean;
88
+ readonly summary: string;
89
+ readonly enumValues?: readonly string[];
90
+ }
91
+
80
92
  export interface HarnessSettingMutationResult {
81
93
  readonly key: string;
82
94
  readonly action: 'set' | 'reset';
@@ -91,6 +103,11 @@ function valuesEqual(left: unknown, right: unknown): boolean {
91
103
  return JSON.stringify(left) === JSON.stringify(right);
92
104
  }
93
105
 
106
+ function previewText(value: string, maxLength = 120): string {
107
+ const normalized = value.replace(/\s+/g, ' ').trim();
108
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1).trimEnd()}...`;
109
+ }
110
+
94
111
  function clampLimit(value: unknown, fallback = DEFAULT_SETTING_LIMIT): number {
95
112
  if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;
96
113
  return Math.max(1, Math.min(500, Math.trunc(value)));
@@ -164,10 +181,30 @@ export function describeHarnessSetting(
164
181
  };
165
182
  }
166
183
 
184
+ export function describeHarnessSettingSummary(
185
+ configManager: Pick<ConfigManager, 'get'>,
186
+ setting: ConfigSetting,
187
+ ): HarnessSettingSummary {
188
+ const value = configManager.get(setting.key as ConfigKey);
189
+ const hostOwned = isExternalHostOwnedSettingKey(setting.key);
190
+ return {
191
+ key: setting.key,
192
+ category: setting.key.split('.')[0] ?? '',
193
+ type: setting.type,
194
+ value: redactHarnessSettingValue(setting.key, value),
195
+ configured: !valuesEqual(value, setting.default),
196
+ writable: !hostOwned,
197
+ visibleInWorkspace: !isAgentHiddenSettingKey(setting.key),
198
+ summary: previewText(setting.description),
199
+ ...(setting.enumValues ? { enumValues: setting.enumValues } : {}),
200
+ };
201
+ }
202
+
167
203
  export function listHarnessSettings(
168
204
  configManager: Pick<ConfigManager, 'get' | 'getSchema'>,
169
205
  filters: HarnessSettingFilters = {},
170
- ): readonly HarnessSettingDescriptor[] {
206
+ options: { readonly includeParameters?: boolean } = {},
207
+ ): readonly (HarnessSettingDescriptor | HarnessSettingSummary)[] {
171
208
  const key = filters.key?.trim();
172
209
  const category = filters.category?.trim();
173
210
  const prefix = filters.prefix?.trim();
@@ -185,7 +222,9 @@ export function listHarnessSettings(
185
222
  }
186
223
  return true;
187
224
  })
188
- .map((setting) => describeHarnessSetting(configManager, setting))
225
+ .map((setting) => options.includeParameters
226
+ ? describeHarnessSetting(configManager, setting)
227
+ : describeHarnessSettingSummary(configManager, setting))
189
228
  .slice(0, limit);
190
229
  }
191
230
 
@@ -358,7 +397,7 @@ export async function resetHarnessSetting(
358
397
  };
359
398
  }
360
399
 
361
- export function formatHarnessSettingList(settings: readonly HarnessSettingDescriptor[]): string {
400
+ export function formatHarnessSettingList(settings: readonly (HarnessSettingDescriptor | HarnessSettingSummary)[]): string {
362
401
  if (settings.length === 0) return 'No settings matched.';
363
402
  return [
364
403
  `Settings (${settings.length})`,
@@ -160,7 +160,7 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
160
160
  const [id] = commandValues(rest);
161
161
  if (!id) return { output: 'Usage: goodvibes-agent knowledge get <source|node|issue id>', exitCode: 2 };
162
162
  const route = `/api/goodvibes-agent/knowledge/items/${encodeURIComponent(id)}`;
163
- const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.itemGet, async (connection) => (
163
+ const result = await runKnowledgeCall(runtime, { ...AGENT_KNOWLEDGE_METHODS.itemGet, route }, async (connection) => (
164
164
  await getAgentKnowledgeJson(connection, route)
165
165
  ));
166
166
  if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
@@ -189,7 +189,7 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
189
189
  const id = values[1];
190
190
  if (!id) return { output: 'Usage: goodvibes-agent knowledge connectors doctor <connectorId>', exitCode: 2 };
191
191
  const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}/doctor`;
192
- const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorDoctor, async (connection) => (
192
+ const result = await runKnowledgeCall(runtime, { ...AGENT_KNOWLEDGE_METHODS.connectorDoctor, route }, async (connection) => (
193
193
  await getAgentKnowledgeJson(connection, route)
194
194
  ));
195
195
  if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
@@ -201,7 +201,7 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
201
201
  const id = values[0];
202
202
  if (id) {
203
203
  const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}`;
204
- const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorGet, async (connection) => (
204
+ const result = await runKnowledgeCall(runtime, { ...AGENT_KNOWLEDGE_METHODS.connectorGet, route }, async (connection) => (
205
205
  await getAgentKnowledgeJson(connection, route)
206
206
  ));
207
207
  if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
@@ -224,7 +224,7 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
224
224
  const [id] = commandValues(rest);
225
225
  if (!id) return { output: 'Usage: goodvibes-agent knowledge connector <connectorId>', exitCode: 2 };
226
226
  const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}`;
227
- const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorGet, async (connection) => (
227
+ const result = await runKnowledgeCall(runtime, { ...AGENT_KNOWLEDGE_METHODS.connectorGet, route }, async (connection) => (
228
228
  await getAgentKnowledgeJson(connection, route)
229
229
  ));
230
230
  if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
@@ -238,7 +238,7 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
238
238
  const [id] = commandValues(rest);
239
239
  if (!id) return { output: 'Usage: goodvibes-agent knowledge connector-doctor <connectorId>', exitCode: 2 };
240
240
  const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}/doctor`;
241
- const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorDoctor, async (connection) => (
241
+ const result = await runKnowledgeCall(runtime, { ...AGENT_KNOWLEDGE_METHODS.connectorDoctor, route }, async (connection) => (
242
242
  await getAgentKnowledgeJson(connection, route)
243
243
  ));
244
244
  if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
@@ -47,6 +47,7 @@ export function formatAgentKnowledgeFailureKind(kind: string): string {
47
47
  if (kind === 'connected_host_unavailable') return 'connected host unavailable';
48
48
  if (kind === 'connected_host_route_unavailable') return 'connected host route unavailable';
49
49
  if (kind === 'connected_host_error') return 'connected host error';
50
+ if (kind === 'scope_contamination') return 'scope contamination';
50
51
  if (kind === 'version_mismatch') return 'version mismatch';
51
52
  return kind.replace(/[_-]+/g, ' ');
52
53
  }
@@ -163,6 +164,7 @@ export function formatEntityList(data: unknown, kind: 'sources' | 'nodes' | 'iss
163
164
  ...values.slice(0, limit).map((value, index) => (
164
165
  kind === 'issues' ? format(value) : ` ${index + 1}. ${format(value)}`
165
166
  )),
167
+ ` route /api/goodvibes-agent/knowledge/${kind}`,
166
168
  ].join('\n');
167
169
  }
168
170
 
@@ -179,6 +181,7 @@ export function formatItem(data: unknown, id: string): string {
179
181
  `Agent Knowledge item ${id}`,
180
182
  ` ${sourceLine(source)}`,
181
183
  relatedKnowledgeCountsLine(relatedEdges, linkedSources, linkedNodes),
184
+ ' route /api/goodvibes-agent/knowledge/items/{id}',
182
185
  ].join('\n');
183
186
  }
184
187
  if (node) {
@@ -186,6 +189,7 @@ export function formatItem(data: unknown, id: string): string {
186
189
  `Agent Knowledge item ${id}`,
187
190
  ` ${nodeLine(node)}`,
188
191
  relatedKnowledgeCountsLine(relatedEdges, linkedSources, linkedNodes),
192
+ ' route /api/goodvibes-agent/knowledge/items/{id}',
189
193
  ].join('\n');
190
194
  }
191
195
  if (issue) {
@@ -193,6 +197,7 @@ export function formatItem(data: unknown, id: string): string {
193
197
  `Agent Knowledge item ${id}`,
194
198
  issueLine(issue),
195
199
  relatedKnowledgeCountsLine(relatedEdges, linkedSources, linkedNodes),
200
+ ' route /api/goodvibes-agent/knowledge/items/{id}',
196
201
  ].join('\n');
197
202
  }
198
203
  return [
@@ -231,6 +236,7 @@ export function formatConnectors(data: unknown): string {
231
236
  return [
232
237
  `Agent Knowledge connectors (${connectors.length})`,
233
238
  ...connectors.map(connectorLine),
239
+ ' route /api/goodvibes-agent/knowledge/connectors',
234
240
  ].join('\n');
235
241
  }
236
242
 
@@ -299,6 +305,7 @@ export function formatAsk(data: unknown, query: string): string {
299
305
  }
300
306
  if (facts.length > 0) lines.push('', `Facts ${facts.length}`);
301
307
  if (gaps.length > 0) lines.push('', `Gaps ${gaps.length}`);
308
+ lines.push('', 'route /api/goodvibes-agent/knowledge/ask');
302
309
  return lines.join('\n');
303
310
  }
304
311
 
@@ -316,6 +323,7 @@ export function formatSearch(data: unknown, query: string): string {
316
323
  return [
317
324
  `Agent Knowledge search ${query}`,
318
325
  ...results.slice(0, 10).map((result, index) => ` ${index + 1}. ${resultLine(result)}`),
326
+ ' route /api/goodvibes-agent/knowledge/search',
319
327
  ].join('\n');
320
328
  }
321
329
 
@@ -385,5 +393,8 @@ export function formatFailure(failure: AgentKnowledgeFailureLike, json: boolean)
385
393
  failure.kind === 'connected_host_route_unavailable'
386
394
  ? ' next update the connected GoodVibes host to the SDK version required by this Agent package.'
387
395
  : null,
396
+ failure.kind === 'scope_contamination'
397
+ ? ' next update the connected GoodVibes host so Agent Knowledge routes return only Agent-owned scope data.'
398
+ : null,
388
399
  ].filter((line): line is string => Boolean(line)).join('\n');
389
400
  }
@@ -1,8 +1,7 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
1
  import { createBrowserAgentSdk } from '@pellux/goodvibes-sdk/browser/agent';
4
2
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
5
3
  import { SDK_VERSION, VERSION } from '../version.ts';
4
+ import { readConnectedHostOperatorToken } from '../runtime/connected-host-auth.ts';
6
5
  import type { ConnectedHostCallMethod } from './agent-knowledge-methods.ts';
7
6
 
8
7
  export type JsonRecord = Record<string, unknown>;
@@ -22,7 +21,13 @@ export interface AgentKnowledgeConnectionRuntime {
22
21
 
23
22
  export interface AgentKnowledgeFailure {
24
23
  readonly ok: false;
25
- readonly kind: 'connected_host_unavailable' | 'auth_required' | 'version_mismatch' | 'connected_host_route_unavailable' | 'connected_host_error';
24
+ readonly kind:
25
+ | 'connected_host_unavailable'
26
+ | 'auth_required'
27
+ | 'version_mismatch'
28
+ | 'connected_host_route_unavailable'
29
+ | 'connected_host_error'
30
+ | 'scope_contamination';
26
31
  readonly error: string;
27
32
  readonly baseUrl: string;
28
33
  readonly route: string;
@@ -56,15 +61,8 @@ export function resolveConnectedHostConnection(runtime: AgentKnowledgeConnection
56
61
  const host = String(runtime.configManager.get('controlPlane.host') ?? '127.0.0.1');
57
62
  const port = Number(runtime.configManager.get('controlPlane.port') ?? 3421);
58
63
  const baseUrl = `http://${host}:${Number.isFinite(port) ? port : 3421}`;
59
- const tokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
60
- if (!existsSync(tokenPath)) return { baseUrl, token: null, tokenPath };
61
- try {
62
- const parsed = JSON.parse(readFileSync(tokenPath, 'utf-8')) as unknown;
63
- const token = isRecord(parsed) && typeof parsed.token === 'string' ? parsed.token : null;
64
- return { baseUrl, token, tokenPath };
65
- } catch {
66
- return { baseUrl, token: null, tokenPath };
67
- }
64
+ const token = readConnectedHostOperatorToken(runtime.homeDirectory);
65
+ return { baseUrl, token: token.token, tokenPath: token.path };
68
66
  }
69
67
 
70
68
  export async function fetchConnectedHostStatus(connection: AgentKnowledgeConnection): Promise<{ readonly ok: boolean; readonly status: number; readonly body: unknown }> {
@@ -122,6 +120,87 @@ export function createAgentSdk(connection: AgentKnowledgeConnection) {
122
120
  });
123
121
  }
124
122
 
123
+ const AGENT_KNOWLEDGE_SCOPE_KEYS = new Set(['spaceid', 'knowledgespaceid']);
124
+ const AGENT_KNOWLEDGE_SCOPE_TEXT_PATTERNS: readonly { readonly label: string; readonly pattern: RegExp }[] = [
125
+ {
126
+ label: 'default knowledge scope id',
127
+ pattern: /["']?(?:knowledge[-_\s]*space[-_\s]*id|knowledgespaceid|space[-_\s]*id|spaceid)["']?\s*[:=]\s*["']?default["']?/i,
128
+ },
129
+ { label: 'host assistant payload marker', pattern: /home\s*assistant/i },
130
+ { label: 'host graph payload marker', pattern: /home\s*graph|homegraph/i },
131
+ ];
132
+
133
+ function normalizedJsonKey(key: string): string {
134
+ return key.replace(/[-_\s]/g, '').toLowerCase();
135
+ }
136
+
137
+ function findAgentKnowledgeTextContamination(value: string): string | null {
138
+ for (const { label, pattern } of AGENT_KNOWLEDGE_SCOPE_TEXT_PATTERNS) {
139
+ if (pattern.test(value)) return label;
140
+ }
141
+ return null;
142
+ }
143
+
144
+ export function findAgentKnowledgeScopeContamination(value: unknown): string | null {
145
+ const seen = new WeakSet<object>();
146
+ const visit = (node: unknown): string | null => {
147
+ if (typeof node === 'string') return findAgentKnowledgeTextContamination(node);
148
+ if (!node || typeof node !== 'object') return null;
149
+ if (seen.has(node)) return null;
150
+ seen.add(node);
151
+ if (Array.isArray(node)) {
152
+ for (const item of node) {
153
+ const nested = visit(item);
154
+ if (nested) return nested;
155
+ }
156
+ return null;
157
+ }
158
+ for (const [key, nestedValue] of Object.entries(node as JsonRecord)) {
159
+ const keyFinding = findAgentKnowledgeTextContamination(key);
160
+ if (keyFinding && keyFinding !== 'default knowledge scope id') return keyFinding;
161
+ const normalizedKey = normalizedJsonKey(key);
162
+ if (
163
+ AGENT_KNOWLEDGE_SCOPE_KEYS.has(normalizedKey)
164
+ && typeof nestedValue === 'string'
165
+ && nestedValue.trim().toLowerCase() === 'default'
166
+ ) {
167
+ return `${key}=default`;
168
+ }
169
+ const nested = visit(nestedValue);
170
+ if (nested) return nested;
171
+ }
172
+ return null;
173
+ };
174
+ return visit(value);
175
+ }
176
+
177
+ export function agentKnowledgeScopeContaminationFailure(
178
+ connection: AgentKnowledgeConnection,
179
+ route: string,
180
+ finding: string,
181
+ ): AgentKnowledgeFailure {
182
+ return {
183
+ ok: false,
184
+ kind: 'scope_contamination',
185
+ error: [
186
+ `Agent Knowledge route returned non-Agent knowledge contamination (${finding}).`,
187
+ 'GoodVibes Agent must use only isolated /api/goodvibes-agent/knowledge/* scope data.',
188
+ ].join(' '),
189
+ baseUrl: connection.baseUrl,
190
+ route,
191
+ };
192
+ }
193
+
194
+ export function validateAgentKnowledgeData<TData>(
195
+ data: TData,
196
+ connection: AgentKnowledgeConnection,
197
+ method: ConnectedHostCallMethod,
198
+ ): AgentKnowledgeResult<TData> {
199
+ const contamination = findAgentKnowledgeScopeContamination(data);
200
+ if (contamination) return agentKnowledgeScopeContaminationFailure(connection, method.route, contamination);
201
+ return { ok: true, kind: method.kind, route: method.route, data };
202
+ }
203
+
125
204
  export async function postAgentKnowledgeJson<TData>(
126
205
  connection: AgentKnowledgeConnection,
127
206
  route: string,
@@ -239,7 +318,7 @@ export async function runKnowledgeCall<TData>(
239
318
  }
240
319
  try {
241
320
  const data = await call(connection);
242
- return { ok: true, kind: method.kind, route: method.route, data };
321
+ return validateAgentKnowledgeData(data, connection, method);
243
322
  } catch (error) {
244
323
  return classifyKnowledgeError(error, connection, method.route);
245
324
  }
@@ -1,10 +1,11 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
2
+ import { dirname } from 'node:path';
3
3
  import { RuntimeEventBus } from '@/runtime/index.ts';
4
4
  import { createShellPathService } from '@/runtime/index.ts';
5
5
  import { listProviderRuntimeSnapshots } from '@pellux/goodvibes-sdk/platform/providers';
6
6
  import { createRuntimeServices } from '../runtime/services.ts';
7
7
  import { createRuntimeStore } from '../runtime/store/index.ts';
8
+ import { readConnectedHostOperatorToken } from '../runtime/connected-host-auth.ts';
8
9
  import { getOnboardingCheckMarkerPath } from '../runtime/onboarding/index.ts';
9
10
  import { CONFIG_SCHEMA } from '../config/index.ts';
10
11
  import { SecretsManager } from '../config/secrets.ts';
@@ -76,14 +77,14 @@ function readAuthPosture(runtime: CliCommandRuntime) {
76
77
  });
77
78
  const userStorePath = shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'auth-users.json');
78
79
  const bootstrapCredentialPath = shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'auth-bootstrap.txt');
79
- const operatorTokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
80
+ const operatorToken = readConnectedHostOperatorToken(runtime.homeDirectory);
80
81
  return {
81
82
  userStorePath,
82
83
  userStorePresent: existsSync(userStorePath),
83
84
  bootstrapCredentialPath,
84
85
  bootstrapCredentialPresent: existsSync(bootstrapCredentialPath),
85
- operatorTokenPath,
86
- operatorTokenPresent: existsSync(operatorTokenPath),
86
+ operatorTokenPath: operatorToken.path,
87
+ operatorTokenPresent: operatorToken.present && Boolean(operatorToken.token),
87
88
  };
88
89
  }
89
90
 
@@ -186,6 +186,9 @@ export async function prepareShellCliRuntime(
186
186
  configManager,
187
187
  homeDirectory: bootstrapHomeDirectory,
188
188
  });
189
+ const effectiveOperatorTokenPath = externalRuntime.operatorToken.present
190
+ ? externalRuntime.operatorToken.path
191
+ : operatorTokenPath;
189
192
  const statusOptions = {
190
193
  configManager,
191
194
  workingDirectory: bootstrapWorkingDir,
@@ -196,8 +199,8 @@ export async function prepareShellCliRuntime(
196
199
  userStorePresent: existsSync(userStorePath),
197
200
  bootstrapCredentialPath,
198
201
  bootstrapCredentialPresent: existsSync(bootstrapCredentialPath),
199
- operatorTokenPath,
200
- operatorTokenPresent: existsSync(operatorTokenPath),
202
+ operatorTokenPath: effectiveOperatorTokenPath,
203
+ operatorTokenPresent: externalRuntime.operatorToken.present,
201
204
  },
202
205
  service,
203
206
  externalRuntime,
@@ -1,8 +1,7 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
1
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
4
2
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
5
3
  import { SDK_VERSION } from '../version.ts';
4
+ import { readConnectedHostOperatorToken } from '../runtime/connected-host-auth.ts';
6
5
 
7
6
  type JsonRecord = Record<string, unknown>;
8
7
 
@@ -47,18 +46,6 @@ function resolveBaseUrl(configManager: Pick<ConfigManager, 'get'>): string {
47
46
  return `http://${host}:${Number.isFinite(port) ? port : 3421}`;
48
47
  }
49
48
 
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
49
  async function fetchJson(
63
50
  url: string,
64
51
  token: string | null,
@@ -90,7 +77,7 @@ export async function inspectCliExternalRuntime(
90
77
  options: CliExternalRuntimeInspectionOptions,
91
78
  ): Promise<CliExternalRuntimeSnapshot> {
92
79
  const baseUrl = resolveBaseUrl(options.configManager);
93
- const token = readOperatorToken(options.homeDirectory);
80
+ const token = readConnectedHostOperatorToken(options.homeDirectory);
94
81
  const timeoutMs = options.timeoutMs ?? 1500;
95
82
  const route = '/api/goodvibes-agent/knowledge/status' as const;
96
83
 
@@ -9,6 +9,7 @@ import { formatProviderModel, getModelIdFromProviderModel } from '../config/prov
9
9
  import { bootstrapRuntime } from '../runtime/bootstrap.ts';
10
10
  import { createRuntimeServices } from '../runtime/services.ts';
11
11
  import { createRuntimeStore } from '../runtime/store/index.ts';
12
+ import { readConnectedHostOperatorToken } from '../runtime/connected-host-auth.ts';
12
13
  import type { RuntimeServices } from '../runtime/services.ts';
13
14
  import { SecretsManager } from '../config/secrets.ts';
14
15
  import { RuntimeEventBus, type TurnEvent } from '@/runtime/index.ts';
@@ -190,14 +191,14 @@ export function readAuthPaths(runtime: CliCommandRuntime) {
190
191
  });
191
192
  const userStorePath = shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'auth-users.json');
192
193
  const bootstrapCredentialPath = shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'auth-bootstrap.txt');
193
- const operatorTokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
194
+ const operatorToken = readConnectedHostOperatorToken(runtime.homeDirectory);
194
195
  return {
195
196
  userStorePath,
196
197
  userStorePresent: existsSync(userStorePath),
197
198
  bootstrapCredentialPath,
198
199
  bootstrapCredentialPresent: existsSync(bootstrapCredentialPath),
199
- operatorTokenPath,
200
- operatorTokenPresent: existsSync(operatorTokenPath),
200
+ operatorTokenPath: operatorToken.path,
201
+ operatorTokenPresent: operatorToken.present && Boolean(operatorToken.token),
201
202
  };
202
203
  }
203
204
 
@@ -21,7 +21,7 @@ export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void
21
21
  'Welcome To GoodVibes Agent',
22
22
  ' /setup - open Agent setup with current settings preloaded',
23
23
  ' /agent - open the Agent operator workspace',
24
- ' /knowledge - inspect isolated Agent Knowledge status, ask, and search',
24
+ ' /knowledge - inspect isolated Agent Knowledge status, ask/search, libraries, map, connectors, and ingest paths',
25
25
  ' /memory - manage Agent-local memory',
26
26
  ' /personas - manage Agent-local personas',
27
27
  ' /skills - manage reusable Agent-local skills',
@@ -209,9 +209,9 @@ function renderKnowledgeAskResult(result: KnowledgeAskResult): string {
209
209
  export const knowledgeCommand: SlashCommand = {
210
210
  name: 'knowledge',
211
211
  aliases: ['know', 'kb'],
212
- description: 'Agent Knowledge: isolated Agent-owned sources, graph, review queue, and compact prompt packets.',
212
+ description: 'Agent Knowledge: isolated Agent-owned status, ask/search, source/node/issue lists, item lookup, map, connectors, ingest, and review queue.',
213
213
  usage: '<subcommand> [args]',
214
- argsHint: 'status|ask|ingest-url --yes|ingest-file --yes|import-bookmarks --yes|list|search|get|queue|review-issue --yes',
214
+ argsHint: 'status|ask|search|list|get|map|connectors|connector|connector-doctor|ingest-url --yes|ingest-file --yes|import-urls --yes|import-bookmarks --yes|import-browser-history --yes|ingest-connector --yes|review-issue --yes|reindex --yes',
215
215
  handler: async (args: string[], context: CommandContext): Promise<void> => {
216
216
  if (args.length === 0 && context.openAgentWorkspace) {
217
217
  context.openAgentWorkspace('knowledge');
@@ -46,24 +46,16 @@ import { registerAgentHarnessTool } from '../tools/agent-harness-tool.ts';
46
46
 
47
47
  const GOODVIBES_AGENT_OPERATOR_POLICY = [
48
48
  '## GoodVibes Agent Operator Policy',
49
- '- Default to serial, proactive assistant work in the main conversation. Answer, inspect, summarize, remember useful non-secret facts, configure Agent-local state, use read-only connected-host/operator routes, and take safe non-destructive actions without creating separate Agent jobs or GoodVibes TUI delegations.',
50
- '- GoodVibes Agent connects to a GoodVibes host owned outside this package. Do not start, stop, restart, install, expose, or mutate connected-host network/listener posture from Agent.',
51
- '- Use the `agent_operator_briefing` tool for read-only main-conversation summaries of connected work plan, approvals, automation, schedules, and scheduler capacity. This tool must not call mutation routes, connected-host lifecycle routes, default knowledge, non-Agent knowledge spaces, separate Agent job creation, or GoodVibes TUI delegation.',
52
- '- When the user explicitly asks Agent to approve, deny, or cancel a specific approval, run/pause/resume a specific automation job, cancel/retry a specific automation run, or run a specific schedule, use the `agent_operator_action` tool with confirm:true and the original user request. It can call only its allowlisted public operator routes and must not create, edit, delete, or discover automation definitions.',
53
- '- Use the `agent_work_plan` tool to keep the visible Agent-local work plan current while working in the main conversation. Create, inspect, and update local work items proactively when useful. Removing items or clearing completed items requires an explicit user request and confirm:true.',
54
- '- Use the `agent_harness` tool to inspect and operate the harness surface itself: Agent workspace action catalog, slash command catalog, model tool catalog, connected-host posture, and Agent settings. Use it to change Agent settings, invoke workspace action mirrors, or invoke slash-command mirrors only when the user explicitly asks; preserve confirm:true and explicitUserRequest for mutations, keep secret-backed settings in the secret manager, and keep connected-host lifecycle/posture externally owned.',
55
- '- Use the `agent_knowledge` tool for Agent Knowledge status, ask, and search from the main conversation. It must use only /api/goodvibes-agent/knowledge/* and must fail closed instead of falling back to default knowledge or non-Agent knowledge spaces.',
56
- '- When the user explicitly asks Agent to add, import, remember, or ingest a URL, URL-list file, local file, bookmarks file, browser history, or connector input into Agent Knowledge, use the `agent_knowledge_ingest` tool with confirm:true and the original user request. It must write only to /api/goodvibes-agent/knowledge/* ingest routes and never to default knowledge or non-Agent knowledge spaces.',
57
- '- Use the `agent_local_registry` tool when a scratchpad note, durable memory, reusable persona, skill, skill bundle, or routine would improve later work. Keep those records Agent-local, non-secret, source/provenance tagged, and reviewable. Notes are for temporary/source-triage context; promote them explicitly into memory, skills, personas, routines, or Agent Knowledge only when that is the correct durable home. Review memory with a confidence score when it should shape later turns. Starting a routine means applying its steps in this same serial conversation, not creating a background job.',
58
- '- When the user explicitly asks Agent to generate an image, video, or other media artifact, use the `agent_media_generate` tool with confirm:true and the original user request. Generated media is stored as artifacts; do not print base64, call default knowledge, use non-Agent knowledge spaces, send channels, create separate Agent jobs, or request GoodVibes TUI delegation for media generation.',
59
- '- When the user explicitly asks Agent to send an alert, message, or notification to configured notification targets, use the `agent_notify` tool with confirm:true and the original user request. Do not infer external notifications from vague suggestions, and never create channel routes or account authorizations from the main conversation.',
60
- '- When the user explicitly asks Agent to send one message to a specific configured channel, route, webhook, or link target, use the `agent_channel_send` tool with confirm:true and the original user request. It can deliver one message through configured delivery strategies, but must not create routes, authorize accounts, manage connected-host hosting, use default knowledge, use non-Agent knowledge spaces, create separate Agent jobs, or request GoodVibes TUI delegation.',
61
- '- When the user explicitly asks for a reminder or asks Agent to schedule a reminder, use the `agent_reminder_schedule` tool with confirm:true and the original user request. That creates one connected schedules.create reminder on the external GoodVibes host. Do not infer reminders from vague suggestions or routine startup, and never create local scheduler jobs.',
62
- '- GoodVibes TUI delegation is never the default Agent reasoning path. Do not delegate planning, research, operations, knowledge, memory, configuration, approvals, automation observability, or ordinary assistant work.',
63
- '- GoodVibes Agent is not the coding TUI. Do not use the `agent` tool to create coding-role Agent jobs or batch job roots from Agent.',
64
- '- When the user explicitly asks to build, implement, fix, patch, or review code, preserve the full original user ask and delegate one build request to GoodVibes TUI through the public shared-session/build-delegation contract. Include clear executionIntent and request delegated review only for explicit build/fix/review work or when the user explicitly asks for delegated Agent review.',
65
- '- Do not narrow explicit build/fix/review requests into design-only, read-only, or no-write work unless the user explicitly requested that limitation. TUI owns file edits, git/worktree work, execution isolation UX, and delegated review coordination.',
66
- '- If a stable public delegation route is unavailable, say that the task needs GoodVibes TUI delegation and report the missing route instead of pretending to implement it locally or creating sibling Agent jobs.',
49
+ '- Work serially in the main conversation by default: answer, inspect, summarize, remember useful non-secret facts, configure Agent-local state, and use safe read-only connected-host/operator routes.',
50
+ '- Connected-host lifecycle is external. Do not start, stop, restart, install, expose, or mutate host listeners/network posture from Agent.',
51
+ '- Read tools: `agent_operator_briefing` for connected work/approvals/automation/schedules, `agent_knowledge` for isolated Agent Knowledge, `agent_harness` for harness catalogs/settings/status.',
52
+ '- State tools: `agent_work_plan` for visible local work items; `agent_local_registry` for Agent-local notes, memory, personas, skills, bundles, and routines. Keep records non-secret, sourced, and reviewable.',
53
+ '- Confirmed tools: use `agent_operator_action`, `agent_knowledge_ingest`, `agent_media_generate`, `agent_notify`, `agent_channel_send`, and `agent_reminder_schedule` only for explicit user requests with confirm:true and explicitUserRequest.',
54
+ '- Agent Knowledge must use only `/api/goodvibes-agent/knowledge/*` and fail closed. Do not use default knowledge or non-Agent knowledge spaces.',
55
+ '- External delivery, media generation, reminders, settings writes, slash-command mirrors, workspace action mirrors, and destructive local changes require explicit user intent and the owning tool/command confirmation.',
56
+ '- Routines run in this serial conversation unless explicitly promoted to a connected schedule. Do not create hidden Agent jobs or local scheduler jobs.',
57
+ '- Do not delegate planning, research, operations, knowledge, memory, configuration, approvals, observability, or ordinary assistant work.',
58
+ '- For explicit build, implement, fix, patch, or review requests, preserve the full original ask and use the public shared-session/build-delegation route. If that route is unavailable, report the missing route instead of pretending to perform the work locally.',
67
59
  ].join('\n');
68
60
 
69
61
  function joinPromptParts(...parts: Array<string | null | undefined>): string {
@@ -20,6 +20,22 @@ export function connectedHostOperatorTokenPath(homeDirectory: string): string {
20
20
  }
21
21
 
22
22
  export function readConnectedHostOperatorToken(homeDirectory: string): ConnectedHostOperatorToken {
23
+ const connectedHostEnvToken = process.env.GOODVIBES_CONNECTED_HOST_TOKEN?.trim();
24
+ if (connectedHostEnvToken) {
25
+ return {
26
+ path: 'env:GOODVIBES_CONNECTED_HOST_TOKEN',
27
+ present: true,
28
+ token: connectedHostEnvToken,
29
+ };
30
+ }
31
+ const legacyEnvToken = process.env.GOODVIBES_DAEMON_TOKEN?.trim();
32
+ if (legacyEnvToken) {
33
+ return {
34
+ path: 'env:GOODVIBES_DAEMON_TOKEN',
35
+ present: true,
36
+ token: legacyEnvToken,
37
+ };
38
+ }
23
39
  const path = connectedHostOperatorTokenPath(homeDirectory);
24
40
  if (!existsSync(path)) return { path, present: false, token: null };
25
41
  try {
@@ -82,11 +82,7 @@ export function validateRegistryToolInvocationForAgentPolicy(args: RegistryToolA
82
82
  }
83
83
 
84
84
  function narrowAnalyzeToolDefinitionForAgentPolicy(tool: Tool): void {
85
- tool.definition.description = [
86
- 'Run local, static project analysis for GoodVibes Agent.',
87
- 'npm registry upgrade checks and hidden secondary LLM diff analysis are disabled in the main conversation.',
88
- 'Delegate package-upgrade and review workflows to GoodVibes TUI when they become build/fix/review work.',
89
- ].join(' ');
85
+ tool.definition.description = 'Run local, static project analysis for GoodVibes Agent.';
90
86
  tool.definition.sideEffects = ['read_fs'];
91
87
 
92
88
  const properties = tool.definition.parameters.properties;
@@ -101,10 +97,7 @@ function narrowAnalyzeToolDefinitionForAgentPolicy(tool: Tool): void {
101
97
  }
102
98
 
103
99
  function narrowRegistryToolDefinitionForAgentPolicy(tool: Tool): void {
104
- tool.definition.description = [
105
- 'Discover and preview GoodVibes Agent skills, agents, and tools.',
106
- 'Full content materialization and arbitrary .goodvibes file reads are disabled in the main conversation.',
107
- ].join(' ');
100
+ tool.definition.description = 'Discover and preview GoodVibes Agent registry entries.';
108
101
  tool.definition.sideEffects = ['read_fs'];
109
102
 
110
103
  const properties = tool.definition.parameters.properties;
@@ -41,13 +41,7 @@ export function createAgentChannelSendTool(
41
41
  return {
42
42
  definition: {
43
43
  name: 'agent_channel_send',
44
- description: [
45
- 'Send one explicitly confirmed message through a configured GoodVibes Agent delivery target from the main conversation.',
46
- 'Use only when the user explicitly asks Agent to send, message, alert, or notify a channel target.',
47
- 'Exactly one target must be supplied: channel, route, webhook, or link.',
48
- 'This tool does not create channel routes, authorize accounts, manage connected-host hosting, use default knowledge, use non-Agent knowledge segments, create separate Agent jobs, run delegated review, or invoke arbitrary routes.',
49
- 'Set confirm:true only for an explicit user request. Otherwise return the preview/confirmation error.',
50
- ].join(' '),
44
+ description: 'Send one confirmed message through one configured Agent target.',
51
45
  parameters: {
52
46
  type: 'object',
53
47
  properties: {
@@ -61,7 +55,7 @@ export function createAgentChannelSendTool(
61
55
  },
62
56
  channel: {
63
57
  type: 'string',
64
- description: 'Optional channel target surface[:route[:label]], such as slack:ops:Ops or telephony:+15551234567. Use exactly one target field.',
58
+ description: 'Optional channel target. Use exactly one target field.',
65
59
  },
66
60
  route: {
67
61
  type: 'string',
@@ -77,7 +71,7 @@ export function createAgentChannelSendTool(
77
71
  },
78
72
  confirm: {
79
73
  type: 'boolean',
80
- description: 'Required true only when the user explicitly asked for this exact channel delivery.',
74
+ description: 'Required true for confirmed delivery.',
81
75
  },
82
76
  explicitUserRequest: {
83
77
  type: 'string',
@@ -7,11 +7,7 @@ const CONTEXT_TOOL_DENIAL = [
7
7
  ].join(' ');
8
8
 
9
9
  export function wrapBlockedContextToolForAgentPolicy(tool: Tool): void {
10
- tool.definition.description = [
11
- 'Blocked in GoodVibes Agent main conversation: non-Agent runtime context.',
12
- 'Use explicit Agent CLI/slash status, compat, setup, and Agent Knowledge commands for product-scoped context.',
13
- 'Default knowledge, non-Agent knowledge segments, and non-Agent runtime assumptions are not Agent fallbacks.',
14
- ].join(' ');
10
+ tool.definition.description = 'Blocked in GoodVibes Agent: non-Agent runtime context.';
15
11
  tool.definition.sideEffects = [];
16
12
  tool.definition.parameters = {
17
13
  type: 'object',
@@ -82,10 +82,7 @@ export function normalizeFindToolInvocationForAgentPolicy(args: FindToolArgs): F
82
82
  }
83
83
 
84
84
  function narrowFindToolDefinitionForAgentPolicy(tool: Tool): void {
85
- tool.definition.description = [
86
- 'Search the project for GoodVibes Agent using serial, gitignore-respecting read-only queries.',
87
- 'Hidden-file scans, symlink traversal, gitignore bypass, broad previews, full symbol dumps, and parallel search are disabled in the main conversation.',
88
- ].join(' ');
85
+ tool.definition.description = 'Search project files with serial, gitignore-respecting read-only queries.';
89
86
  tool.definition.sideEffects = ['read_fs'];
90
87
  tool.definition.concurrency = 'serial';
91
88