@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
@@ -11,6 +11,7 @@ export interface AgentHarnessUiSurfaceArgs {
11
11
  readonly target?: unknown;
12
12
  readonly key?: unknown;
13
13
  readonly prefix?: unknown;
14
+ readonly includeParameters?: unknown;
14
15
  readonly limit?: unknown;
15
16
  readonly pane?: unknown;
16
17
  }
@@ -216,7 +217,7 @@ const UI_SURFACES: readonly UiSurfaceDefinition[] = [
216
217
  id: 'knowledge-panel',
217
218
  label: 'Knowledge Panel',
218
219
  kind: 'workspace',
219
- summary: 'Agent Knowledge operator surface for isolated status, source libraries, graph review, ask/search, and ingest forms.',
220
+ summary: 'Agent Knowledge operator surface for isolated status, source/node/issue libraries, item lookup, map review, connectors, ask/search, and ingest forms.',
220
221
  command: '/knowledge',
221
222
  preferredModelRoute: 'Use agent_knowledge, agent_knowledge_ingest, workspace_actions, or run_command /knowledge for concrete model operation.',
222
223
  parameters: ['pane'],
@@ -727,22 +728,28 @@ function resolveHarnessUiSurface(args: AgentHarnessUiSurfaceArgs): UiSurfaceReso
727
728
  return null;
728
729
  }
729
730
 
730
- function describeSurface(context: CommandContext, surface: UiSurfaceDefinition, lookup?: UiSurfaceLookup): Record<string, unknown> {
731
+ function describeSurface(
732
+ context: CommandContext,
733
+ surface: UiSurfaceDefinition,
734
+ options: { readonly includeParameters?: boolean; readonly lookup?: UiSurfaceLookup } = {},
735
+ ): Record<string, unknown> {
731
736
  return {
732
737
  id: surface.id,
733
738
  label: surface.label,
734
739
  kind: surface.kind,
735
740
  summary: surface.summary,
736
741
  command: surface.command,
737
- ...(lookup ? { lookup } : {}),
742
+ ...(options.lookup ? { lookup: options.lookup } : {}),
738
743
  preferredModelRoute: surface.preferredModelRoute,
739
- parameters: surface.parameters ?? [],
740
744
  available: surface.available(context),
741
- policy: {
742
- effect: 'visible-ui-navigation',
743
- confirmation: 'agent_harness mode:"open_ui_surface" requires confirm:true and explicitUserRequest.',
744
- boundary: 'UI surface routing opens the same visible Agent shell surface the user can open. Use first-class model tools, settings modes, workspace actions, or confirmed slash-command mirrors for actual operations.',
745
- },
745
+ ...(options.includeParameters ? {
746
+ parameters: surface.parameters ?? [],
747
+ policy: {
748
+ effect: 'visible-ui-navigation',
749
+ confirmation: 'agent_harness mode:"open_ui_surface" requires confirm:true and explicitUserRequest.',
750
+ boundary: 'UI surface routing opens the same visible Agent shell surface the user can open. Use first-class model tools, settings modes, workspace actions, or confirmed slash-command mirrors for actual operations.',
751
+ },
752
+ } : {}),
746
753
  };
747
754
  }
748
755
 
@@ -753,15 +760,16 @@ export function totalHarnessUiSurfaces(): number {
753
760
  export function listHarnessUiSurfaces(context: CommandContext, args: AgentHarnessUiSurfaceArgs): readonly Record<string, unknown>[] {
754
761
  const query = readString(args.query);
755
762
  const limit = readLimit(args.limit, 200);
763
+ const includeParameters = args.includeParameters === true;
756
764
  return UI_SURFACES
757
- .map((surface) => describeSurface(context, surface))
765
+ .map((surface) => describeSurface(context, surface, { includeParameters }))
758
766
  .filter((surface) => surfaceMatches(surface, query))
759
767
  .slice(0, limit);
760
768
  }
761
769
 
762
770
  export function describeHarnessUiSurface(context: CommandContext, args: AgentHarnessUiSurfaceArgs): Record<string, unknown> | null {
763
771
  const resolved = resolveHarnessUiSurface(args);
764
- if (resolved?.status === 'found') return describeSurface(context, resolved.surface, resolved.lookup);
772
+ if (resolved?.status === 'found') return describeSurface(context, resolved.surface, { includeParameters: true, lookup: resolved.lookup });
765
773
  if (resolved?.status === 'ambiguous') {
766
774
  return { status: 'ambiguous', input: resolved.input, candidates: resolved.candidates };
767
775
  }
@@ -783,6 +791,6 @@ export async function openHarnessUiSurface(context: CommandContext, args: AgentH
783
791
  const routed = await resolved.surface.open(context, args);
784
792
  return {
785
793
  ...routed,
786
- descriptor: describeSurface(context, resolved.surface, resolved.lookup),
794
+ descriptor: describeSurface(context, resolved.surface, { includeParameters: true, lookup: resolved.lookup }),
787
795
  };
788
796
  }
@@ -0,0 +1,260 @@
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 previewText(value: string, maxLength = 120): string {
101
+ const normalized = value.replace(/\s+/g, ' ').trim();
102
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1).trimEnd()}...`;
103
+ }
104
+
105
+ function selectedRoutineFromArgs(
106
+ snapshot: AgentWorkspaceRuntimeSnapshot,
107
+ args: AgentHarnessWorkspaceActionArgs,
108
+ ): AgentWorkspaceLocalLibraryItem | null {
109
+ const fields = readFieldMap(args.fields);
110
+ const routineId = readString(args.recordId) || readString(fields.routineId) || readString(fields.id);
111
+ if (!routineId) return null;
112
+ return snapshot.localRoutines.find((routine) => routine.id === routineId || routine.name.toLowerCase() === routineId.toLowerCase()) ?? null;
113
+ }
114
+
115
+ export function buildWorkspaceEditorContext(context: CommandContext, args: AgentHarnessWorkspaceActionArgs): WorkspaceEditorContext {
116
+ try {
117
+ const snapshot = buildAgentWorkspaceRuntimeSnapshot(context);
118
+ return {
119
+ runtimeStarterTemplates: snapshot.runtimeStarterTemplates,
120
+ selectedRoutine: selectedRoutineFromArgs(snapshot, args),
121
+ };
122
+ } catch {
123
+ return {
124
+ runtimeStarterTemplates: [],
125
+ selectedRoutine: null,
126
+ };
127
+ }
128
+ }
129
+
130
+ export function createWorkspaceEditor(
131
+ editorKind: AgentWorkspaceEditorKind,
132
+ editorContext: WorkspaceEditorContext | null,
133
+ ): AgentWorkspaceLocalEditor | null {
134
+ return createAgentWorkspaceEditor(editorKind, {
135
+ runtimeStarterTemplates: editorContext?.runtimeStarterTemplates ?? [],
136
+ selectedRoutine: editorKind === 'routine-schedule' ? editorContext?.selectedRoutine ?? null : null,
137
+ });
138
+ }
139
+
140
+ export function describeWorkspaceAction(
141
+ category: AgentWorkspaceCategory,
142
+ action: AgentWorkspaceAction,
143
+ options: { readonly includeEditor?: boolean; readonly editorContext?: WorkspaceEditorContext | null; readonly lookup?: WorkspaceActionLookup } = {},
144
+ ): Record<string, unknown> {
145
+ const editor = options.includeEditor && action.editorKind ? createWorkspaceEditor(action.editorKind, options.editorContext ?? null) : null;
146
+ return {
147
+ id: action.id,
148
+ categoryId: category.id,
149
+ category: category.label,
150
+ group: category.group,
151
+ label: action.label,
152
+ detail: action.detail,
153
+ kind: action.kind,
154
+ safety: action.safety,
155
+ ...(action.command ? { command: action.command } : {}),
156
+ ...(action.targetCategoryId ? { targetCategoryId: action.targetCategoryId } : {}),
157
+ ...(action.editorKind ? { editorKind: action.editorKind } : {}),
158
+ ...(action.localKind ? { localKind: action.localKind } : {}),
159
+ ...(action.localOperation ? { localOperation: action.localOperation } : {}),
160
+ ...(options.lookup ? { lookup: options.lookup } : {}),
161
+ ...(editor ? { editor: describeWorkspaceEditor(editor) } : {}),
162
+ ...(action.kind === 'local-selection' || action.kind === 'local-operation' ? {
163
+ modelExecution: describeLocalWorkspaceModelExecution(action),
164
+ } : {}),
165
+ ...(action.kind === 'editor' && action.editorKind ? {
166
+ modelExecution: describeWorkspaceEditorModelExecution(action.editorKind),
167
+ } : {}),
168
+ };
169
+ }
170
+
171
+ function describeWorkspaceActionSummary(
172
+ category: AgentWorkspaceCategory,
173
+ action: AgentWorkspaceAction,
174
+ ): Record<string, unknown> {
175
+ return {
176
+ id: action.id,
177
+ categoryId: category.id,
178
+ category: category.label,
179
+ group: category.group,
180
+ label: action.label,
181
+ summary: previewText(action.detail),
182
+ kind: action.kind,
183
+ safety: action.safety,
184
+ ...(action.command ? { command: action.command } : {}),
185
+ ...(action.targetCategoryId ? { targetCategoryId: action.targetCategoryId } : {}),
186
+ ...(action.editorKind ? { editorKind: action.editorKind } : {}),
187
+ ...(action.localKind ? { localKind: action.localKind } : {}),
188
+ ...(action.localOperation ? { localOperation: action.localOperation } : {}),
189
+ };
190
+ }
191
+
192
+ export function listWorkspaceActions(
193
+ context: CommandContext,
194
+ args: AgentHarnessWorkspaceActionArgs,
195
+ ): readonly Record<string, unknown>[] {
196
+ const query = readString(args.query);
197
+ const categoryId = readString(args.categoryId || args.category);
198
+ const limit = readLimit(args.limit, 200);
199
+ const includeEditor = args.includeParameters === true;
200
+ const editorContext = includeEditor ? buildWorkspaceEditorContext(context, args) : null;
201
+ const source = query
202
+ ? searchAgentWorkspaceActions(AGENT_WORKSPACE_CATEGORIES, query).map((result) => ({ category: result.category, action: result.action }))
203
+ : allWorkspaceActions();
204
+ return source
205
+ .filter((entry) => !categoryId || entry.category.id === categoryId)
206
+ .slice(0, limit)
207
+ .map((entry) => includeEditor
208
+ ? describeWorkspaceAction(entry.category, entry.action, { includeEditor, editorContext })
209
+ : describeWorkspaceActionSummary(entry.category, entry.action));
210
+ }
211
+
212
+ function workspaceActionLookupFromArgs(args: AgentHarnessWorkspaceActionArgs): { readonly source: WorkspaceActionLookup['source']; readonly input: string } | null {
213
+ const actionId = readString(args.actionId);
214
+ if (actionId) return { source: 'actionId', input: actionId };
215
+ const command = readString(args.command);
216
+ if (command) return { source: 'command', input: command };
217
+ const target = readString(args.target);
218
+ if (target) return { source: 'target', input: target };
219
+ const query = readString(args.query);
220
+ return query ? { source: 'query', input: query } : null;
221
+ }
222
+
223
+ function describeWorkspaceActionCandidates(
224
+ entries: readonly { readonly category: AgentWorkspaceCategory; readonly action: AgentWorkspaceAction }[],
225
+ ): readonly { readonly actionId: string; readonly categoryId: string; readonly label: string; readonly command?: string }[] {
226
+ return entries.slice(0, 8).map((entry) => ({
227
+ actionId: entry.action.id,
228
+ categoryId: entry.category.id,
229
+ label: entry.action.label,
230
+ ...(entry.action.command ? { command: entry.action.command } : {}),
231
+ }));
232
+ }
233
+
234
+ export function resolveWorkspaceActionDetail(args: AgentHarnessWorkspaceActionArgs): WorkspaceActionResolution | null {
235
+ const lookup = workspaceActionLookupFromArgs(args);
236
+ const categoryId = readString(args.categoryId || args.category);
237
+ if (!lookup) return null;
238
+ const entries = allWorkspaceActions().filter((entry) => !categoryId || entry.category.id === categoryId);
239
+ const normalized = lookup.input.toLowerCase();
240
+ const commandInput = lookup.source === 'command' ? lookup.input.trim() : '';
241
+
242
+ const exactId = entries.find((entry) => entry.action.id === lookup.input);
243
+ if (exactId) return { status: 'found', ...exactId, lookup: { ...lookup, resolvedBy: 'id' } };
244
+ const exactLabel = entries.find((entry) => entry.action.label === lookup.input);
245
+ if (exactLabel) return { status: 'found', ...exactLabel, lookup: { ...lookup, resolvedBy: 'label' } };
246
+ const exactCommand = commandInput ? entries.find((entry) => entry.action.command === commandInput) : null;
247
+ if (exactCommand) return { status: 'found', ...exactCommand, lookup: { ...lookup, resolvedBy: 'command' } };
248
+
249
+ const insensitiveId = entries.find((entry) => entry.action.id.toLowerCase() === normalized);
250
+ if (insensitiveId) return { status: 'found', ...insensitiveId, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } };
251
+ const insensitiveLabel = entries.find((entry) => entry.action.label.toLowerCase() === normalized);
252
+ if (insensitiveLabel) return { status: 'found', ...insensitiveLabel, lookup: { ...lookup, resolvedBy: 'case-insensitive-label' } };
253
+
254
+ const searched = searchAgentWorkspaceActions(AGENT_WORKSPACE_CATEGORIES, lookup.input)
255
+ .map((result) => ({ category: result.category, action: result.action }))
256
+ .filter((entry) => !categoryId || entry.category.id === categoryId);
257
+ if (searched.length === 1) return { status: 'found', ...searched[0]!, lookup: { ...lookup, resolvedBy: 'search' } };
258
+ if (searched.length > 1) return { status: 'ambiguous', input: lookup.input, candidates: describeWorkspaceActionCandidates(searched) };
259
+ return null;
260
+ }
@@ -261,13 +261,7 @@ export function createAgentKnowledgeIngestTool(
261
261
  return {
262
262
  definition: {
263
263
  name: 'agent_knowledge_ingest',
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.',
270
- ].join(' '),
264
+ description: 'Ingest one confirmed source into isolated Agent Knowledge.',
271
265
  parameters: {
272
266
  type: 'object',
273
267
  properties: {
@@ -282,7 +276,7 @@ export function createAgentKnowledgeIngestTool(
282
276
  },
283
277
  path: {
284
278
  type: 'string',
285
- description: 'Local file/import path for file, urls_file, bookmarks_file, or connector source kinds.',
279
+ description: 'Local file or import path.',
286
280
  },
287
281
  title: {
288
282
  type: 'string',
@@ -299,7 +293,7 @@ export function createAgentKnowledgeIngestTool(
299
293
  },
300
294
  connectorId: {
301
295
  type: 'string',
302
- description: 'Connector id for connector ingest, or optional connector id override for file ingest.',
296
+ description: 'Connector id.',
303
297
  },
304
298
  input: {
305
299
  description: 'Connector input as JSON-compatible data or a JSON/text string.',
@@ -340,7 +334,7 @@ export function createAgentKnowledgeIngestTool(
340
334
  },
341
335
  explicitUserRequest: {
342
336
  type: 'string',
343
- description: 'Short quote or summary of the user request that authorized this Agent Knowledge ingest.',
337
+ description: 'User request authorizing this ingest.',
344
338
  },
345
339
  },
346
340
  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'],
@@ -79,12 +123,7 @@ export function createAgentKnowledgeTool(
79
123
  return {
80
124
  definition: {
81
125
  name: 'agent_knowledge',
82
- 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.',
87
- ].join(' '),
126
+ description: 'Read isolated Agent Knowledge.',
88
127
  parameters: {
89
128
  type: 'object',
90
129
  properties: {
@@ -95,11 +134,19 @@ export function createAgentKnowledgeTool(
95
134
  },
96
135
  query: {
97
136
  type: 'string',
98
- description: 'Question/query for ask or search.',
137
+ description: 'Question/query for ask, search, or map filtering.',
138
+ },
139
+ id: {
140
+ type: 'string',
141
+ description: 'Agent Knowledge source, node, or issue id for action item.',
142
+ },
143
+ connectorId: {
144
+ type: 'string',
145
+ description: 'Connector id for action connector or connector_doctor. id may also be used.',
99
146
  },
100
147
  limit: {
101
148
  type: 'number',
102
- description: 'Maximum source/search results, capped at 25.',
149
+ description: 'Maximum result count.',
103
150
  },
104
151
  mode: {
105
152
  type: 'string',
@@ -125,13 +172,16 @@ export function createAgentKnowledgeTool(
125
172
  route: '/api/goodvibes-agent/knowledge/*',
126
173
  }));
127
174
  }
175
+ let method: ConnectedHostCallMethod = AGENT_KNOWLEDGE_METHODS.status;
128
176
  try {
129
177
  const sdk = createAgentSdk(connection);
130
178
  if (args.action === 'status') {
179
+ method = AGENT_KNOWLEDGE_METHODS.status;
131
180
  const data = await sdk.knowledge.status();
132
- return toolOutput(formatStatus(data));
181
+ return validatedToolOutput(data, connection, method, formatStatus);
133
182
  }
134
183
  if (args.action === 'ask') {
184
+ method = AGENT_KNOWLEDGE_METHODS.ask;
135
185
  const query = readString(args.query);
136
186
  if (!query) return toolFailure('query is required for Agent Knowledge ask.');
137
187
  const data = await sdk.knowledge.ask({
@@ -142,19 +192,64 @@ export function createAgentKnowledgeTool(
142
192
  includeConfidence: true,
143
193
  includeLinkedObjects: true,
144
194
  });
145
- return toolOutput(formatAsk(data, query));
195
+ return validatedToolOutput(data, connection, method, (validatedData) => formatAsk(validatedData, query));
196
+ }
197
+ if (args.action === 'search') {
198
+ method = AGENT_KNOWLEDGE_METHODS.search;
199
+ const query = readString(args.query);
200
+ if (!query) return toolFailure('query is required for Agent Knowledge search.');
201
+ const data = await sdk.knowledge.search({ query, limit: readLimit(args.limit, 10) });
202
+ return validatedToolOutput(data, connection, method, (validatedData) => formatSearch(validatedData, query));
203
+ }
204
+ if (args.action === 'sources' || args.action === 'nodes' || args.action === 'issues') {
205
+ const kind = args.action;
206
+ const limit = readLimit(args.limit, 25, 500);
207
+ method = kind === 'sources'
208
+ ? AGENT_KNOWLEDGE_METHODS.sourcesList
209
+ : kind === 'nodes'
210
+ ? AGENT_KNOWLEDGE_METHODS.nodesList
211
+ : AGENT_KNOWLEDGE_METHODS.issuesList;
212
+ const data = await getAgentKnowledgeJson(connection, method.route, { limit });
213
+ return validatedToolOutput(data, connection, method, (validatedData) => formatEntityList(validatedData, kind, limit));
214
+ }
215
+ if (args.action === 'item') {
216
+ method = AGENT_KNOWLEDGE_METHODS.itemGet;
217
+ const id = readString(args.id);
218
+ if (!id) return toolFailure('id is required for Agent Knowledge item lookup.');
219
+ const route = `/api/goodvibes-agent/knowledge/items/${encodeURIComponent(id)}`;
220
+ const data = await getAgentKnowledgeJson(connection, route);
221
+ return validatedToolOutput(data, connection, { ...method, route }, (validatedData) => formatItem(validatedData, id));
222
+ }
223
+ if (args.action === 'map') {
224
+ method = AGENT_KNOWLEDGE_METHODS.map;
225
+ const data = await getAgentKnowledgeJson(connection, method.route, {
226
+ limit: readLimit(args.limit, 50, 500),
227
+ query: readString(args.query),
228
+ });
229
+ return validatedToolOutput(data, connection, method, formatMap);
230
+ }
231
+ if (args.action === 'connectors') {
232
+ method = AGENT_KNOWLEDGE_METHODS.connectorsList;
233
+ const data = await getAgentKnowledgeJson(connection, method.route);
234
+ return validatedToolOutput(data, connection, method, formatConnectors);
235
+ }
236
+ if (args.action === 'connector' || args.action === 'connector_doctor') {
237
+ method = args.action === 'connector'
238
+ ? AGENT_KNOWLEDGE_METHODS.connectorGet
239
+ : AGENT_KNOWLEDGE_METHODS.connectorDoctor;
240
+ const connectorId = readString(args.connectorId) || readString(args.id);
241
+ if (!connectorId) return toolFailure('connectorId or id is required for Agent Knowledge connector lookup.');
242
+ const suffix = args.action === 'connector_doctor' ? '/doctor' : '';
243
+ const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(connectorId)}${suffix}`;
244
+ const data = await getAgentKnowledgeJson(connection, route);
245
+ return validatedToolOutput(data, connection, { ...method, route }, (validatedData) => (
246
+ args.action === 'connector'
247
+ ? formatConnector(validatedData, connectorId)
248
+ : formatConnectorDoctor(validatedData, connectorId)
249
+ ));
146
250
  }
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));
251
+ return toolFailure(`Unknown Agent Knowledge action. Valid values ${ACTIONS.join(', ')}.`);
152
252
  } 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
253
  return classifyToolKnowledgeError(error, connection, method);
159
254
  }
160
255
  },
@@ -735,11 +735,7 @@ export function createAgentLocalRegistryTool(shellPaths: ShellPathService, memor
735
735
  return {
736
736
  definition: {
737
737
  name: 'agent_local_registry',
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.',
742
- ].join(' '),
738
+ description: 'Inspect or update Agent-local records.',
743
739
  parameters: {
744
740
  type: 'object',
745
741
  properties: {
@@ -761,13 +757,13 @@ export function createAgentLocalRegistryTool(shellPaths: ShellPathService, memor
761
757
  steps: { type: 'string', description: 'Routine steps.' },
762
758
  skills: { type: 'array', items: { type: 'string' }, description: 'Skill ids for skill_bundle create/update.' },
763
759
  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.' },
760
+ requiresEnv: { type: 'array', items: { type: 'string' }, description: 'Required environment variable names.' },
765
761
  requiresCommands: { type: 'array', items: { type: 'string' }, description: 'Command names required by a skill or routine.' },
766
762
  triggers: { type: 'array', items: { type: 'string' } },
767
763
  tags: { type: 'array', items: { type: 'string' } },
768
764
  reason: { type: 'string' },
769
765
  enabled: { type: 'boolean' },
770
- activate: { type: 'boolean', description: 'Activate a created/updated persona, or clear it when updating the active persona with false.' },
766
+ activate: { type: 'boolean', description: 'Activate or clear the persona.' },
771
767
  provenance: { type: 'string' },
772
768
  confirm: { type: 'boolean', description: 'Required true for delete after an explicit user request.' },
773
769
  explicitUserRequest: { type: 'string', description: 'Exact user request or faithful short summary authorizing delete.' },
@@ -52,13 +52,7 @@ export function createAgentMediaGenerateTool(
52
52
  return {
53
53
  definition: {
54
54
  name: 'agent_media_generate',
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.',
61
- ].join(' '),
55
+ description: 'Generate one confirmed image or video artifact.',
62
56
  parameters: {
63
57
  type: 'object',
64
58
  properties: {
@@ -68,7 +62,7 @@ export function createAgentMediaGenerateTool(
68
62
  },
69
63
  providerId: {
70
64
  type: 'string',
71
- description: 'Optional configured media provider id. If omitted, the first generation-capable provider is used.',
65
+ description: 'Optional media provider id.',
72
66
  },
73
67
  modelId: {
74
68
  type: 'string',