@pellux/goodvibes-agent 1.0.31 → 1.0.33

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 (63) hide show
  1. package/CHANGELOG.md +38 -3
  2. package/README.md +7 -5
  3. package/dist/package/main.js +7346 -2349
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +6 -2
  6. package/docs/connected-host.md +26 -5
  7. package/docs/getting-started.md +29 -9
  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 +20 -8
  13. package/package.json +8 -3
  14. package/release/live-verification/live-verification.json +148 -0
  15. package/release/live-verification/live-verification.md +187 -0
  16. package/release/performance-snapshot.json +57 -0
  17. package/release/release-notes.md +28 -0
  18. package/release/release-readiness.json +581 -0
  19. package/src/cli/agent-knowledge-command.ts +5 -5
  20. package/src/cli/agent-knowledge-format.ts +11 -0
  21. package/src/cli/agent-knowledge-runtime.ts +92 -13
  22. package/src/cli/bundle-command.ts +5 -4
  23. package/src/cli/entrypoint.ts +5 -2
  24. package/src/cli/external-runtime.ts +2 -15
  25. package/src/cli/management.ts +4 -3
  26. package/src/input/commands/guidance-runtime.ts +1 -1
  27. package/src/input/commands/knowledge.ts +2 -2
  28. package/src/runtime/bootstrap.ts +2 -2
  29. package/src/runtime/connected-host-auth.ts +16 -0
  30. package/src/tools/agent-channel-send-tool.ts +5 -7
  31. package/src/tools/agent-harness-channel-metadata.ts +177 -0
  32. package/src/tools/agent-harness-connected-host-status.ts +1 -1
  33. package/src/tools/agent-harness-delegation-posture.ts +216 -0
  34. package/src/tools/agent-harness-keybinding-metadata.ts +57 -22
  35. package/src/tools/agent-harness-mcp-metadata.ts +246 -0
  36. package/src/tools/agent-harness-media-posture.ts +282 -0
  37. package/src/tools/agent-harness-metadata.ts +21 -4
  38. package/src/tools/agent-harness-model-routing.ts +501 -0
  39. package/src/tools/agent-harness-notification-metadata.ts +217 -0
  40. package/src/tools/agent-harness-operator-methods.ts +285 -0
  41. package/src/tools/agent-harness-pairing-posture.ts +265 -0
  42. package/src/tools/agent-harness-provider-account-metadata.ts +203 -0
  43. package/src/tools/agent-harness-release-evidence.ts +364 -0
  44. package/src/tools/agent-harness-release-readiness.ts +298 -0
  45. package/src/tools/agent-harness-security-posture.ts +646 -0
  46. package/src/tools/agent-harness-service-posture.ts +201 -0
  47. package/src/tools/agent-harness-session-metadata.ts +282 -0
  48. package/src/tools/agent-harness-setup-posture.ts +295 -0
  49. package/src/tools/agent-harness-tool-schema.ts +103 -27
  50. package/src/tools/agent-harness-tool.ts +209 -236
  51. package/src/tools/agent-harness-ui-surface-metadata.ts +1 -1
  52. package/src/tools/agent-harness-workspace-actions.ts +232 -0
  53. package/src/tools/agent-knowledge-ingest-tool.ts +6 -8
  54. package/src/tools/agent-knowledge-tool.ts +122 -23
  55. package/src/tools/agent-local-registry-tool.ts +4 -5
  56. package/src/tools/agent-media-generate-tool.ts +4 -6
  57. package/src/tools/agent-notify-tool.ts +5 -6
  58. package/src/tools/agent-operator-action-tool.ts +6 -8
  59. package/src/tools/agent-operator-briefing-tool.ts +3 -4
  60. package/src/tools/agent-reminder-schedule-tool.ts +6 -7
  61. package/src/tools/agent-tool-policy-guard.ts +8 -17
  62. package/src/tools/agent-work-plan-tool.ts +3 -4
  63. package/src/version.ts +2 -2
@@ -0,0 +1,232 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+ import { createAgentWorkspaceEditor } from '../input/agent-workspace-activation.ts';
3
+ import { AGENT_WORKSPACE_CATEGORIES } from '../input/agent-workspace-categories.ts';
4
+ import { searchAgentWorkspaceActions } from '../input/agent-workspace-search.ts';
5
+ import { buildAgentWorkspaceRuntimeSnapshot } from '../input/agent-workspace-snapshot.ts';
6
+ import type { AgentWorkspaceAction, AgentWorkspaceCategory, AgentWorkspaceEditorKind, AgentWorkspaceLocalEditor, AgentWorkspaceLocalLibraryItem, AgentWorkspaceRuntimeSnapshot } from '../input/agent-workspace-types.ts';
7
+ import { describeLocalWorkspaceModelExecution } from './agent-harness-local-operations.ts';
8
+ import { describeWorkspaceEditorModelExecution } from './agent-harness-workspace-editor-execution.ts';
9
+
10
+ export { AGENT_WORKSPACE_CATEGORIES };
11
+
12
+ export interface AgentHarnessWorkspaceActionArgs {
13
+ readonly query?: unknown;
14
+ readonly command?: unknown;
15
+ readonly actionId?: unknown;
16
+ readonly recordId?: unknown;
17
+ readonly fields?: unknown;
18
+ readonly target?: unknown;
19
+ readonly category?: unknown;
20
+ readonly categoryId?: unknown;
21
+ readonly includeParameters?: unknown;
22
+ readonly limit?: unknown;
23
+ }
24
+
25
+ export interface WorkspaceEditorContext {
26
+ readonly runtimeStarterTemplates: AgentWorkspaceRuntimeSnapshot['runtimeStarterTemplates'];
27
+ readonly selectedRoutine: AgentWorkspaceLocalLibraryItem | null;
28
+ }
29
+
30
+ export interface WorkspaceActionLookup {
31
+ readonly source: 'actionId' | 'command' | 'target' | 'query';
32
+ readonly input: string;
33
+ readonly resolvedBy: 'id' | 'case-insensitive-id' | 'label' | 'case-insensitive-label' | 'command' | 'search';
34
+ }
35
+
36
+ export type WorkspaceActionResolution =
37
+ | {
38
+ readonly status: 'found';
39
+ readonly category: AgentWorkspaceCategory;
40
+ readonly action: AgentWorkspaceAction;
41
+ readonly lookup: WorkspaceActionLookup;
42
+ }
43
+ | {
44
+ readonly status: 'ambiguous';
45
+ readonly input: string;
46
+ readonly candidates: readonly { readonly actionId: string; readonly categoryId: string; readonly label: string; readonly command?: string }[];
47
+ };
48
+
49
+ function readString(value: unknown): string {
50
+ return typeof value === 'string' ? value.trim() : '';
51
+ }
52
+
53
+ function readLimit(value: unknown, fallback: number): number {
54
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
55
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
56
+ return Math.max(1, Math.min(500, Math.trunc(parsed)));
57
+ }
58
+
59
+ function readFieldMap(value: unknown): Readonly<Record<string, string>> {
60
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return {};
61
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, typeof entry === 'string' ? entry : String(entry)]));
62
+ }
63
+
64
+ export function allWorkspaceActions(): ReadonlyArray<{
65
+ readonly category: AgentWorkspaceCategory;
66
+ readonly action: AgentWorkspaceAction;
67
+ }> {
68
+ return AGENT_WORKSPACE_CATEGORIES.flatMap((category) => category.actions.map((action) => ({ category, action })));
69
+ }
70
+
71
+ export function describeWorkspaceCategory(category: AgentWorkspaceCategory): Record<string, unknown> {
72
+ return {
73
+ id: category.id,
74
+ group: category.group,
75
+ label: category.label,
76
+ summary: category.summary,
77
+ detail: category.detail,
78
+ actions: category.actions.length,
79
+ };
80
+ }
81
+
82
+ export function describeWorkspaceEditor(editor: AgentWorkspaceLocalEditor): Record<string, unknown> {
83
+ return {
84
+ kind: editor.kind,
85
+ mode: editor.mode,
86
+ title: editor.title,
87
+ message: editor.message,
88
+ fields: editor.fields.map((field) => ({
89
+ id: field.id,
90
+ label: field.label,
91
+ required: field.required,
92
+ multiline: field.multiline,
93
+ hint: field.hint,
94
+ redact: field.redact === true,
95
+ default: field.redact ? '<redacted>' : field.value,
96
+ })),
97
+ };
98
+ }
99
+
100
+ function selectedRoutineFromArgs(
101
+ snapshot: AgentWorkspaceRuntimeSnapshot,
102
+ args: AgentHarnessWorkspaceActionArgs,
103
+ ): AgentWorkspaceLocalLibraryItem | null {
104
+ const fields = readFieldMap(args.fields);
105
+ const routineId = readString(args.recordId) || readString(fields.routineId) || readString(fields.id);
106
+ if (!routineId) return null;
107
+ return snapshot.localRoutines.find((routine) => routine.id === routineId || routine.name.toLowerCase() === routineId.toLowerCase()) ?? null;
108
+ }
109
+
110
+ export function buildWorkspaceEditorContext(context: CommandContext, args: AgentHarnessWorkspaceActionArgs): WorkspaceEditorContext {
111
+ try {
112
+ const snapshot = buildAgentWorkspaceRuntimeSnapshot(context);
113
+ return {
114
+ runtimeStarterTemplates: snapshot.runtimeStarterTemplates,
115
+ selectedRoutine: selectedRoutineFromArgs(snapshot, args),
116
+ };
117
+ } catch {
118
+ return {
119
+ runtimeStarterTemplates: [],
120
+ selectedRoutine: null,
121
+ };
122
+ }
123
+ }
124
+
125
+ export function createWorkspaceEditor(
126
+ editorKind: AgentWorkspaceEditorKind,
127
+ editorContext: WorkspaceEditorContext | null,
128
+ ): AgentWorkspaceLocalEditor | null {
129
+ return createAgentWorkspaceEditor(editorKind, {
130
+ runtimeStarterTemplates: editorContext?.runtimeStarterTemplates ?? [],
131
+ selectedRoutine: editorKind === 'routine-schedule' ? editorContext?.selectedRoutine ?? null : null,
132
+ });
133
+ }
134
+
135
+ export function describeWorkspaceAction(
136
+ category: AgentWorkspaceCategory,
137
+ action: AgentWorkspaceAction,
138
+ options: { readonly includeEditor?: boolean; readonly editorContext?: WorkspaceEditorContext | null; readonly lookup?: WorkspaceActionLookup } = {},
139
+ ): Record<string, unknown> {
140
+ const editor = options.includeEditor && action.editorKind ? createWorkspaceEditor(action.editorKind, options.editorContext ?? null) : null;
141
+ return {
142
+ id: action.id,
143
+ categoryId: category.id,
144
+ category: category.label,
145
+ group: category.group,
146
+ label: action.label,
147
+ detail: action.detail,
148
+ kind: action.kind,
149
+ safety: action.safety,
150
+ ...(action.command ? { command: action.command } : {}),
151
+ ...(action.targetCategoryId ? { targetCategoryId: action.targetCategoryId } : {}),
152
+ ...(action.editorKind ? { editorKind: action.editorKind } : {}),
153
+ ...(action.localKind ? { localKind: action.localKind } : {}),
154
+ ...(action.localOperation ? { localOperation: action.localOperation } : {}),
155
+ ...(options.lookup ? { lookup: options.lookup } : {}),
156
+ ...(editor ? { editor: describeWorkspaceEditor(editor) } : {}),
157
+ ...(action.kind === 'local-selection' || action.kind === 'local-operation' ? {
158
+ modelExecution: describeLocalWorkspaceModelExecution(action),
159
+ } : {}),
160
+ ...(action.kind === 'editor' && action.editorKind ? {
161
+ modelExecution: describeWorkspaceEditorModelExecution(action.editorKind),
162
+ } : {}),
163
+ };
164
+ }
165
+
166
+ export function listWorkspaceActions(
167
+ context: CommandContext,
168
+ args: AgentHarnessWorkspaceActionArgs,
169
+ ): readonly Record<string, unknown>[] {
170
+ const query = readString(args.query);
171
+ const categoryId = readString(args.categoryId || args.category);
172
+ const limit = readLimit(args.limit, 200);
173
+ const includeEditor = args.includeParameters === true;
174
+ const editorContext = includeEditor ? buildWorkspaceEditorContext(context, args) : null;
175
+ const source = query
176
+ ? searchAgentWorkspaceActions(AGENT_WORKSPACE_CATEGORIES, query).map((result) => ({ category: result.category, action: result.action }))
177
+ : allWorkspaceActions();
178
+ return source
179
+ .filter((entry) => !categoryId || entry.category.id === categoryId)
180
+ .slice(0, limit)
181
+ .map((entry) => describeWorkspaceAction(entry.category, entry.action, { includeEditor, editorContext }));
182
+ }
183
+
184
+ function workspaceActionLookupFromArgs(args: AgentHarnessWorkspaceActionArgs): { readonly source: WorkspaceActionLookup['source']; readonly input: string } | null {
185
+ const actionId = readString(args.actionId);
186
+ if (actionId) return { source: 'actionId', input: actionId };
187
+ const command = readString(args.command);
188
+ if (command) return { source: 'command', input: command };
189
+ const target = readString(args.target);
190
+ if (target) return { source: 'target', input: target };
191
+ const query = readString(args.query);
192
+ return query ? { source: 'query', input: query } : null;
193
+ }
194
+
195
+ function describeWorkspaceActionCandidates(
196
+ entries: readonly { readonly category: AgentWorkspaceCategory; readonly action: AgentWorkspaceAction }[],
197
+ ): readonly { readonly actionId: string; readonly categoryId: string; readonly label: string; readonly command?: string }[] {
198
+ return entries.slice(0, 8).map((entry) => ({
199
+ actionId: entry.action.id,
200
+ categoryId: entry.category.id,
201
+ label: entry.action.label,
202
+ ...(entry.action.command ? { command: entry.action.command } : {}),
203
+ }));
204
+ }
205
+
206
+ export function resolveWorkspaceActionDetail(args: AgentHarnessWorkspaceActionArgs): WorkspaceActionResolution | null {
207
+ const lookup = workspaceActionLookupFromArgs(args);
208
+ const categoryId = readString(args.categoryId || args.category);
209
+ if (!lookup) return null;
210
+ const entries = allWorkspaceActions().filter((entry) => !categoryId || entry.category.id === categoryId);
211
+ const normalized = lookup.input.toLowerCase();
212
+ const commandInput = lookup.source === 'command' ? lookup.input.trim() : '';
213
+
214
+ const exactId = entries.find((entry) => entry.action.id === lookup.input);
215
+ if (exactId) return { status: 'found', ...exactId, lookup: { ...lookup, resolvedBy: 'id' } };
216
+ const exactLabel = entries.find((entry) => entry.action.label === lookup.input);
217
+ if (exactLabel) return { status: 'found', ...exactLabel, lookup: { ...lookup, resolvedBy: 'label' } };
218
+ const exactCommand = commandInput ? entries.find((entry) => entry.action.command === commandInput) : null;
219
+ if (exactCommand) return { status: 'found', ...exactCommand, lookup: { ...lookup, resolvedBy: 'command' } };
220
+
221
+ const insensitiveId = entries.find((entry) => entry.action.id.toLowerCase() === normalized);
222
+ if (insensitiveId) return { status: 'found', ...insensitiveId, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } };
223
+ const insensitiveLabel = entries.find((entry) => entry.action.label.toLowerCase() === normalized);
224
+ if (insensitiveLabel) return { status: 'found', ...insensitiveLabel, lookup: { ...lookup, resolvedBy: 'case-insensitive-label' } };
225
+
226
+ const searched = searchAgentWorkspaceActions(AGENT_WORKSPACE_CATEGORIES, lookup.input)
227
+ .map((result) => ({ category: result.category, action: result.action }))
228
+ .filter((entry) => !categoryId || entry.category.id === categoryId);
229
+ if (searched.length === 1) return { status: 'found', ...searched[0]!, lookup: { ...lookup, resolvedBy: 'search' } };
230
+ if (searched.length > 1) return { status: 'ambiguous', input: lookup.input, candidates: describeWorkspaceActionCandidates(searched) };
231
+ return null;
232
+ }
@@ -262,11 +262,9 @@ export function createAgentKnowledgeIngestTool(
262
262
  definition: {
263
263
  name: 'agent_knowledge_ingest',
264
264
  description: [
265
- 'Ingest explicit sources into isolated GoodVibes Agent Knowledge from the main conversation.',
266
- 'Use only when the user explicitly asks Agent to add, remember, import, or ingest a URL, local file, URL-list file, bookmarks file, browser history, or connector input into its Agent Knowledge.',
267
- 'This writes only to /api/goodvibes-agent/knowledge/* ingest routes on the connected GoodVibes host.',
268
- 'It must never call default knowledge, non-Agent knowledge spaces, separate Agent jobs, local schedulers, or delegated review.',
269
- 'Set confirm:true only for an explicit user request. Otherwise return the preview/confirmation error.',
265
+ 'Ingest one confirmed source into isolated Agent Knowledge.',
266
+ 'Use only for explicit ingest/import requests.',
267
+ 'Supports URL, file, bookmark, history, and connector sources.',
270
268
  ].join(' '),
271
269
  parameters: {
272
270
  type: 'object',
@@ -282,7 +280,7 @@ export function createAgentKnowledgeIngestTool(
282
280
  },
283
281
  path: {
284
282
  type: 'string',
285
- description: 'Local file/import path for file, urls_file, bookmarks_file, or connector source kinds.',
283
+ description: 'Local file or import path.',
286
284
  },
287
285
  title: {
288
286
  type: 'string',
@@ -299,7 +297,7 @@ export function createAgentKnowledgeIngestTool(
299
297
  },
300
298
  connectorId: {
301
299
  type: 'string',
302
- description: 'Connector id for connector ingest, or optional connector id override for file ingest.',
300
+ description: 'Connector id.',
303
301
  },
304
302
  input: {
305
303
  description: 'Connector input as JSON-compatible data or a JSON/text string.',
@@ -340,7 +338,7 @@ export function createAgentKnowledgeIngestTool(
340
338
  },
341
339
  explicitUserRequest: {
342
340
  type: 'string',
343
- description: 'Short quote or summary of the user request that authorized this Agent Knowledge ingest.',
341
+ description: 'User request authorizing this ingest.',
344
342
  },
345
343
  },
346
344
  required: ['confirm', 'explicitUserRequest'],
@@ -4,29 +4,62 @@ import type { ShellPathService } from '@/runtime/index.ts';
4
4
  import {
5
5
  createAgentSdk,
6
6
  classifyKnowledgeError,
7
+ getAgentKnowledgeJson,
7
8
  resolveConnectedHostConnection,
9
+ validateAgentKnowledgeData,
8
10
  type AgentKnowledgeFailure,
9
11
  type AgentKnowledgeConnectionRuntime,
10
12
  } from '../cli/agent-knowledge-runtime.ts';
11
13
  import { AGENT_KNOWLEDGE_METHODS, type ConnectedHostCallMethod } from '../cli/agent-knowledge-methods.ts';
12
14
  import {
13
15
  formatAsk,
16
+ formatConnector,
17
+ formatConnectorDoctor,
18
+ formatConnectors,
19
+ formatEntityList,
14
20
  formatFailure,
21
+ formatItem,
22
+ formatMap,
15
23
  formatSearch,
16
24
  formatStatus,
17
25
  } from '../cli/agent-knowledge-format.ts';
18
26
 
19
- export type AgentKnowledgeToolAction = 'status' | 'ask' | 'search';
27
+ export type AgentKnowledgeToolAction =
28
+ | 'status'
29
+ | 'ask'
30
+ | 'search'
31
+ | 'sources'
32
+ | 'nodes'
33
+ | 'issues'
34
+ | 'item'
35
+ | 'map'
36
+ | 'connectors'
37
+ | 'connector'
38
+ | 'connector_doctor';
20
39
  export type AgentKnowledgeAskMode = 'concise' | 'standard' | 'detailed';
21
40
 
22
41
  export interface AgentKnowledgeToolArgs {
23
42
  readonly action?: unknown;
24
43
  readonly query?: unknown;
44
+ readonly id?: unknown;
45
+ readonly connectorId?: unknown;
25
46
  readonly limit?: unknown;
26
47
  readonly mode?: unknown;
27
48
  }
28
49
 
29
- const ACTIONS: readonly AgentKnowledgeToolAction[] = ['status', 'ask', 'search'];
50
+ const ACTIONS: readonly AgentKnowledgeToolAction[] = [
51
+ 'status',
52
+ 'ask',
53
+ 'search',
54
+ 'sources',
55
+ 'nodes',
56
+ 'issues',
57
+ 'item',
58
+ 'map',
59
+ 'connectors',
60
+ 'connector',
61
+ 'connector_doctor',
62
+ ];
30
63
  const ASK_MODES: readonly AgentKnowledgeAskMode[] = ['concise', 'standard', 'detailed'];
31
64
 
32
65
  function isAction(value: unknown): value is AgentKnowledgeToolAction {
@@ -37,11 +70,11 @@ function readString(value: unknown): string {
37
70
  return typeof value === 'string' ? value.trim() : '';
38
71
  }
39
72
 
40
- function readLimit(value: unknown, fallback: number): number {
41
- if (typeof value === 'number' && Number.isInteger(value) && value > 0) return Math.min(value, 25);
73
+ function readLimit(value: unknown, fallback: number, max = 25): number {
74
+ if (typeof value === 'number' && Number.isInteger(value) && value > 0) return Math.min(value, max);
42
75
  if (typeof value === 'string' && value.trim()) {
43
76
  const parsed = Number(value);
44
- if (Number.isInteger(parsed) && parsed > 0) return Math.min(parsed, 25);
77
+ if (Number.isInteger(parsed) && parsed > 0) return Math.min(parsed, max);
45
78
  }
46
79
  return fallback;
47
80
  }
@@ -72,6 +105,17 @@ async function classifyToolKnowledgeError(
72
105
  return toolFailure(formatKnowledgeFailure(await classifyKnowledgeError(error, connection, method.route)));
73
106
  }
74
107
 
108
+ function validatedToolOutput<TData>(
109
+ data: TData,
110
+ connection: ReturnType<typeof resolveConnectedHostConnection>,
111
+ method: ConnectedHostCallMethod,
112
+ format: (validatedData: TData) => string,
113
+ ): { readonly success: true; readonly output: string } | { readonly success: false; readonly error: string } {
114
+ const validated = validateAgentKnowledgeData(data, connection, method);
115
+ if (!validated.ok) return toolFailure(formatKnowledgeFailure(validated));
116
+ return toolOutput(format(validated.data));
117
+ }
118
+
75
119
  export function createAgentKnowledgeTool(
76
120
  shellPaths: ShellPathService,
77
121
  configManager: AgentKnowledgeConnectionRuntime['configManager'],
@@ -80,10 +124,9 @@ export function createAgentKnowledgeTool(
80
124
  definition: {
81
125
  name: 'agent_knowledge',
82
126
  description: [
83
- 'Read isolated GoodVibes Agent Knowledge from the main conversation.',
84
- 'Use for Agent-owned knowledge status, ask, and search only.',
85
- 'This tool calls /api/goodvibes-agent/knowledge/* on the connected GoodVibes host and must never fall back to default knowledge or non-Agent knowledge spaces.',
86
- 'It is read-only and does not ingest, reindex, mutate, or create background work.',
127
+ 'Read isolated Agent Knowledge.',
128
+ 'Use for status, ask/search, source/node/issue lists, item lookup, map summary, and connector inspection.',
129
+ 'Read-only.',
87
130
  ].join(' '),
88
131
  parameters: {
89
132
  type: 'object',
@@ -95,11 +138,19 @@ export function createAgentKnowledgeTool(
95
138
  },
96
139
  query: {
97
140
  type: 'string',
98
- description: 'Question/query for ask or search.',
141
+ description: 'Question/query for ask, search, or map filtering.',
142
+ },
143
+ id: {
144
+ type: 'string',
145
+ description: 'Agent Knowledge source, node, or issue id for action item.',
146
+ },
147
+ connectorId: {
148
+ type: 'string',
149
+ description: 'Connector id for action connector or connector_doctor. id may also be used.',
99
150
  },
100
151
  limit: {
101
152
  type: 'number',
102
- description: 'Maximum source/search results, capped at 25.',
153
+ description: 'Maximum result count.',
103
154
  },
104
155
  mode: {
105
156
  type: 'string',
@@ -125,13 +176,16 @@ export function createAgentKnowledgeTool(
125
176
  route: '/api/goodvibes-agent/knowledge/*',
126
177
  }));
127
178
  }
179
+ let method: ConnectedHostCallMethod = AGENT_KNOWLEDGE_METHODS.status;
128
180
  try {
129
181
  const sdk = createAgentSdk(connection);
130
182
  if (args.action === 'status') {
183
+ method = AGENT_KNOWLEDGE_METHODS.status;
131
184
  const data = await sdk.knowledge.status();
132
- return toolOutput(formatStatus(data));
185
+ return validatedToolOutput(data, connection, method, formatStatus);
133
186
  }
134
187
  if (args.action === 'ask') {
188
+ method = AGENT_KNOWLEDGE_METHODS.ask;
135
189
  const query = readString(args.query);
136
190
  if (!query) return toolFailure('query is required for Agent Knowledge ask.');
137
191
  const data = await sdk.knowledge.ask({
@@ -142,19 +196,64 @@ export function createAgentKnowledgeTool(
142
196
  includeConfidence: true,
143
197
  includeLinkedObjects: true,
144
198
  });
145
- return toolOutput(formatAsk(data, query));
199
+ return validatedToolOutput(data, connection, method, (validatedData) => formatAsk(validatedData, query));
200
+ }
201
+ if (args.action === 'search') {
202
+ method = AGENT_KNOWLEDGE_METHODS.search;
203
+ const query = readString(args.query);
204
+ if (!query) return toolFailure('query is required for Agent Knowledge search.');
205
+ const data = await sdk.knowledge.search({ query, limit: readLimit(args.limit, 10) });
206
+ return validatedToolOutput(data, connection, method, (validatedData) => formatSearch(validatedData, query));
207
+ }
208
+ if (args.action === 'sources' || args.action === 'nodes' || args.action === 'issues') {
209
+ const kind = args.action;
210
+ const limit = readLimit(args.limit, 25, 500);
211
+ method = kind === 'sources'
212
+ ? AGENT_KNOWLEDGE_METHODS.sourcesList
213
+ : kind === 'nodes'
214
+ ? AGENT_KNOWLEDGE_METHODS.nodesList
215
+ : AGENT_KNOWLEDGE_METHODS.issuesList;
216
+ const data = await getAgentKnowledgeJson(connection, method.route, { limit });
217
+ return validatedToolOutput(data, connection, method, (validatedData) => formatEntityList(validatedData, kind, limit));
218
+ }
219
+ if (args.action === 'item') {
220
+ method = AGENT_KNOWLEDGE_METHODS.itemGet;
221
+ const id = readString(args.id);
222
+ if (!id) return toolFailure('id is required for Agent Knowledge item lookup.');
223
+ const route = `/api/goodvibes-agent/knowledge/items/${encodeURIComponent(id)}`;
224
+ const data = await getAgentKnowledgeJson(connection, route);
225
+ return validatedToolOutput(data, connection, { ...method, route }, (validatedData) => formatItem(validatedData, id));
226
+ }
227
+ if (args.action === 'map') {
228
+ method = AGENT_KNOWLEDGE_METHODS.map;
229
+ const data = await getAgentKnowledgeJson(connection, method.route, {
230
+ limit: readLimit(args.limit, 50, 500),
231
+ query: readString(args.query),
232
+ });
233
+ return validatedToolOutput(data, connection, method, formatMap);
234
+ }
235
+ if (args.action === 'connectors') {
236
+ method = AGENT_KNOWLEDGE_METHODS.connectorsList;
237
+ const data = await getAgentKnowledgeJson(connection, method.route);
238
+ return validatedToolOutput(data, connection, method, formatConnectors);
239
+ }
240
+ if (args.action === 'connector' || args.action === 'connector_doctor') {
241
+ method = args.action === 'connector'
242
+ ? AGENT_KNOWLEDGE_METHODS.connectorGet
243
+ : AGENT_KNOWLEDGE_METHODS.connectorDoctor;
244
+ const connectorId = readString(args.connectorId) || readString(args.id);
245
+ if (!connectorId) return toolFailure('connectorId or id is required for Agent Knowledge connector lookup.');
246
+ const suffix = args.action === 'connector_doctor' ? '/doctor' : '';
247
+ const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(connectorId)}${suffix}`;
248
+ const data = await getAgentKnowledgeJson(connection, route);
249
+ return validatedToolOutput(data, connection, { ...method, route }, (validatedData) => (
250
+ args.action === 'connector'
251
+ ? formatConnector(validatedData, connectorId)
252
+ : formatConnectorDoctor(validatedData, connectorId)
253
+ ));
146
254
  }
147
- const query = readString(args.query);
148
- if (!query) return toolFailure('query is required for Agent Knowledge search.');
149
- const method = AGENT_KNOWLEDGE_METHODS.search;
150
- const data = await sdk.knowledge.search({ query, limit: readLimit(args.limit, 10) });
151
- return toolOutput(formatSearch(data, query));
255
+ return toolFailure(`Unknown Agent Knowledge action. Valid values ${ACTIONS.join(', ')}.`);
152
256
  } catch (error) {
153
- const method = args.action === 'status'
154
- ? AGENT_KNOWLEDGE_METHODS.status
155
- : args.action === 'ask'
156
- ? AGENT_KNOWLEDGE_METHODS.ask
157
- : AGENT_KNOWLEDGE_METHODS.search;
158
257
  return classifyToolKnowledgeError(error, connection, method);
159
258
  }
160
259
  },
@@ -736,9 +736,8 @@ export function createAgentLocalRegistryTool(shellPaths: ShellPathService, memor
736
736
  definition: {
737
737
  name: 'agent_local_registry',
738
738
  description: [
739
- 'Inspect and maintain GoodVibes Agent-local notes, memory, personas, skills, and routines from the main conversation.',
740
- 'Use this for safe self-improvement: capture scratchpad notes, remember durable non-secret facts, create or refine reusable behavior, bundle related skills, enable skills/routines, choose personas, review/stale records, and start routines in the same serial conversation.',
741
- 'Destructive record deletion requires confirm:true plus explicitUserRequest. This tool cannot create schedules, mutate connected hosts, send messages, run background jobs, or delegate build work.',
739
+ 'Inspect or update Agent-local records: memory, notes, personas, skills, bundles, and routines.',
740
+ 'Deletion requires confirm:true plus explicitUserRequest.',
742
741
  ].join(' '),
743
742
  parameters: {
744
743
  type: 'object',
@@ -761,13 +760,13 @@ export function createAgentLocalRegistryTool(shellPaths: ShellPathService, memor
761
760
  steps: { type: 'string', description: 'Routine steps.' },
762
761
  skills: { type: 'array', items: { type: 'string' }, description: 'Skill ids for skill_bundle create/update.' },
763
762
  skillIds: { type: 'array', items: { type: 'string' }, description: 'Skill ids for skill_bundle create/update.' },
764
- requiresEnv: { type: 'array', items: { type: 'string' }, description: 'Environment variable names required by a skill or routine. Values are never stored.' },
763
+ requiresEnv: { type: 'array', items: { type: 'string' }, description: 'Required environment variable names.' },
765
764
  requiresCommands: { type: 'array', items: { type: 'string' }, description: 'Command names required by a skill or routine.' },
766
765
  triggers: { type: 'array', items: { type: 'string' } },
767
766
  tags: { type: 'array', items: { type: 'string' } },
768
767
  reason: { type: 'string' },
769
768
  enabled: { type: 'boolean' },
770
- activate: { type: 'boolean', description: 'Activate a created/updated persona, or clear it when updating the active persona with false.' },
769
+ activate: { type: 'boolean', description: 'Activate or clear the persona.' },
771
770
  provenance: { type: 'string' },
772
771
  confirm: { type: 'boolean', description: 'Required true for delete after an explicit user request.' },
773
772
  explicitUserRequest: { type: 'string', description: 'Exact user request or faithful short summary authorizing delete.' },
@@ -53,11 +53,9 @@ export function createAgentMediaGenerateTool(
53
53
  definition: {
54
54
  name: 'agent_media_generate',
55
55
  description: [
56
- 'Generate image or video artifacts through configured GoodVibes Agent media providers from the main conversation.',
57
- 'Use only when the user explicitly asks Agent to generate media.',
58
- 'Generated bytes are stored as GoodVibes artifacts and the response returns artifact ids; do not print inline base64.',
59
- 'This tool does not use default knowledge, non-Agent knowledge segments, connected-host lifecycle routes, separate Agent jobs, delegated review, channel sends, or arbitrary route invocation.',
60
- 'Set confirm:true only for an explicit user request. Otherwise return the preview/confirmation error.',
56
+ 'Generate one confirmed image or video artifact.',
57
+ 'Use only for explicit media generation requests.',
58
+ 'Returns artifact ids, not inline base64.',
61
59
  ].join(' '),
62
60
  parameters: {
63
61
  type: 'object',
@@ -68,7 +66,7 @@ export function createAgentMediaGenerateTool(
68
66
  },
69
67
  providerId: {
70
68
  type: 'string',
71
- description: 'Optional configured media provider id. If omitted, the first generation-capable provider is used.',
69
+ description: 'Optional media provider id.',
72
70
  },
73
71
  modelId: {
74
72
  type: 'string',
@@ -79,17 +79,16 @@ export function createAgentNotifyTool(
79
79
  definition: {
80
80
  name: 'agent_notify',
81
81
  description: [
82
- 'Send one plain-text notification to configured GoodVibes Agent webhook notification targets from the main conversation.',
83
- 'Use only when the user explicitly asks Agent to notify, message, alert, or send a configured notification.',
84
- 'This uses Agent-local notification webhook targets; it does not create channel routes, authorize accounts, manage connected-host hosting, create separate Agent jobs, run delegated review, or write to default knowledge or non-Agent knowledge segments.',
85
- 'Set confirm:true only for an explicit user request. Otherwise return the preview/confirmation error.',
82
+ 'Send one confirmed plain-text notification.',
83
+ 'Use only for explicit notify/message/alert requests.',
84
+ 'Uses existing targets only.',
86
85
  ].join(' '),
87
86
  parameters: {
88
87
  type: 'object',
89
88
  properties: {
90
89
  message: {
91
90
  type: 'string',
92
- description: 'Plain-text notification body to send to configured Agent notification webhook targets.',
91
+ description: 'Plain-text notification body.',
93
92
  },
94
93
  confirm: {
95
94
  type: 'boolean',
@@ -97,7 +96,7 @@ export function createAgentNotifyTool(
97
96
  },
98
97
  explicitUserRequest: {
99
98
  type: 'string',
100
- description: 'Short quote or summary of the user request that authorized this external notification.',
99
+ description: 'User request authorizing this notification.',
101
100
  },
102
101
  },
103
102
  required: ['message', 'confirm', 'explicitUserRequest'],
@@ -36,11 +36,9 @@ export function createAgentOperatorActionTool(
36
36
  definition: {
37
37
  name: 'agent_operator_action',
38
38
  description: [
39
- 'Perform one explicit, confirmed connected-host operator action from the main conversation.',
40
- 'Allowed actions are approvals.approve, approvals.deny, approvals.cancel, automation.jobs.run, automation.jobs.pause, automation.jobs.resume, automation.runs.cancel, automation.runs.retry, and schedules.run.',
41
- 'Use only when the user explicitly asks for that exact approval, automation job, automation run, or schedule action.',
42
- 'This tool never creates, edits, deletes, or discovers automation definitions; never manages connected-host hosting; never uses default knowledge, non-Agent knowledge segments, separate Agent jobs, delegated review, or arbitrary route invocation.',
43
- 'Set confirm:true only for an explicit user request. Otherwise return the preview/confirmation error.',
39
+ 'Run one confirmed allowlisted operator action.',
40
+ 'Use only for explicit approval, automation, or schedule requests.',
41
+ 'No automation definition edits or host lifecycle.',
44
42
  ].join(' '),
45
43
  parameters: {
46
44
  type: 'object',
@@ -52,7 +50,7 @@ export function createAgentOperatorActionTool(
52
50
  },
53
51
  targetId: {
54
52
  type: 'string',
55
- description: 'Generic target id. Prefer the specific approvalId, jobId, runId, or scheduleId field when known.',
53
+ description: 'Generic target id.',
56
54
  },
57
55
  approvalId: {
58
56
  type: 'string',
@@ -60,7 +58,7 @@ export function createAgentOperatorActionTool(
60
58
  },
61
59
  jobId: {
62
60
  type: 'string',
63
- description: 'Automation job id for automation.jobs.run, automation.jobs.pause, or automation.jobs.resume.',
61
+ description: 'Automation job id.',
64
62
  },
65
63
  runId: {
66
64
  type: 'string',
@@ -80,7 +78,7 @@ export function createAgentOperatorActionTool(
80
78
  },
81
79
  confirm: {
82
80
  type: 'boolean',
83
- description: 'Required true only when the user explicitly asked for this exact operator action.',
81
+ description: 'Required true for confirmed operator action.',
84
82
  },
85
83
  explicitUserRequest: {
86
84
  type: 'string',
@@ -176,10 +176,9 @@ export function createAgentOperatorBriefingTool(
176
176
  definition: {
177
177
  name: 'agent_operator_briefing',
178
178
  description: [
179
- 'Read connected GoodVibes operator state for a concise main-conversation briefing.',
180
- 'Use when the user asks what needs attention, what is pending, what is scheduled, or what the operator status is.',
181
- 'This is read-only and calls only public work-plan, approvals, automation, schedules, and scheduler routes.',
182
- 'It never uses default knowledge, non-Agent knowledge segments, channel send routes, mutation routes, connected-host lifecycle, separate Agent jobs, or delegated review.',
179
+ 'Read connected Agent operator state for a concise briefing.',
180
+ 'Use for pending attention, approvals, automation, schedules, or status.',
181
+ 'Read-only.',
183
182
  ].join(' '),
184
183
  parameters: {
185
184
  type: 'object',