@pellux/goodvibes-agent 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +8 -4
- package/dist/package/main.js +14046 -12557
- package/docs/README.md +4 -2
- package/docs/channels-remote-and-api.md +4 -0
- package/docs/connected-host.md +2 -0
- package/docs/getting-started.md +6 -2
- package/docs/knowledge-artifacts-and-multimodal.md +5 -3
- package/docs/project-planning.md +3 -1
- package/docs/providers-and-routing.md +3 -0
- package/docs/release-and-publishing.md +7 -6
- package/docs/tools-and-commands.md +97 -2
- package/docs/voice-and-live-tts.md +2 -0
- package/package.json +1 -1
- package/src/agent/harness-control.ts +266 -0
- package/src/cli/local-library-command.ts +25 -5
- package/src/cli/routines-command.ts +15 -3
- package/src/config/agent-settings-policy.ts +44 -0
- package/src/input/agent-workspace-activation.ts +14 -6
- package/src/input/agent-workspace-knowledge-url-editor.ts +4 -11
- package/src/input/commands/agent-local-library-args.ts +52 -0
- package/src/input/commands/agent-skills-runtime.ts +4 -29
- package/src/input/commands/mcp-runtime.ts +62 -15
- package/src/input/commands/operator-runtime.ts +139 -3
- package/src/input/commands/personas-runtime.ts +4 -29
- package/src/input/commands/routines-runtime.ts +4 -29
- package/src/input/session-picker-modal.ts +3 -2
- package/src/input/settings-modal-agent-policy.ts +5 -44
- package/src/runtime/bootstrap.ts +3 -0
- package/src/tools/agent-harness-local-operations.ts +233 -0
- package/src/tools/agent-harness-metadata.ts +300 -0
- package/src/tools/agent-harness-tool.ts +774 -0
- package/src/tools/agent-local-registry-requirements.ts +18 -0
- package/src/tools/agent-local-registry-tool.ts +88 -34
- package/src/version.ts +1 -1
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
+
import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
3
|
+
import type { CommandContext, CommandRegistry, SlashCommand } from '../input/command-registry.ts';
|
|
4
|
+
import { buildAgentWorkspaceCommandEditorSubmission, isAgentWorkspaceCommandEditorKind } from '../input/agent-workspace-command-editor.ts';
|
|
5
|
+
import { createAgentWorkspaceEditor } from '../input/agent-workspace-activation.ts';
|
|
6
|
+
import { AGENT_WORKSPACE_CATEGORIES } from '../input/agent-workspace-categories.ts';
|
|
7
|
+
import { isAffirmative, splitList } from '../input/agent-workspace-editors.ts';
|
|
8
|
+
import { createAgentWorkspaceLearnedBehavior } from '../input/agent-workspace-learned-behavior.ts';
|
|
9
|
+
import { searchAgentWorkspaceActions } from '../input/agent-workspace-search.ts';
|
|
10
|
+
import { buildAgentWorkspaceRuntimeSnapshot } from '../input/agent-workspace-snapshot.ts';
|
|
11
|
+
import type {
|
|
12
|
+
AgentWorkspaceAction,
|
|
13
|
+
AgentWorkspaceCategory,
|
|
14
|
+
AgentWorkspaceEditorKind,
|
|
15
|
+
AgentWorkspaceLocalEditor,
|
|
16
|
+
AgentWorkspaceLocalLibraryItem,
|
|
17
|
+
AgentWorkspaceRuntimeSnapshot,
|
|
18
|
+
} from '../input/agent-workspace-types.ts';
|
|
19
|
+
import { parseSlashCommand } from '../input/slash-command-parser.ts';
|
|
20
|
+
import { describeLocalWorkspaceModelExecution, runLocalWorkspaceAction } from './agent-harness-local-operations.ts';
|
|
21
|
+
import {
|
|
22
|
+
blockedConnectedHostCapabilities,
|
|
23
|
+
connectedHostCapabilityMap,
|
|
24
|
+
connectedHostRouteFamilies,
|
|
25
|
+
describeCommandPolicy,
|
|
26
|
+
settingsPolicySummary,
|
|
27
|
+
} from './agent-harness-metadata.ts';
|
|
28
|
+
import {
|
|
29
|
+
formatHarnessError,
|
|
30
|
+
getHarnessSetting,
|
|
31
|
+
listHarnessSettings,
|
|
32
|
+
resetHarnessSetting,
|
|
33
|
+
setHarnessSetting,
|
|
34
|
+
} from '../agent/harness-control.ts';
|
|
35
|
+
import { resolveAgentConnectedHostConnection } from '../agent/routine-schedule-promotion.ts';
|
|
36
|
+
|
|
37
|
+
type AgentHarnessMode =
|
|
38
|
+
| 'summary'
|
|
39
|
+
| 'commands'
|
|
40
|
+
| 'command'
|
|
41
|
+
| 'run_command'
|
|
42
|
+
| 'settings'
|
|
43
|
+
| 'get_setting'
|
|
44
|
+
| 'set_setting'
|
|
45
|
+
| 'reset_setting'
|
|
46
|
+
| 'workspace'
|
|
47
|
+
| 'workspace_categories'
|
|
48
|
+
| 'workspace_actions'
|
|
49
|
+
| 'workspace_action'
|
|
50
|
+
| 'run_workspace_action'
|
|
51
|
+
| 'tools'
|
|
52
|
+
| 'connected_host';
|
|
53
|
+
|
|
54
|
+
interface AgentHarnessToolArgs {
|
|
55
|
+
readonly mode?: unknown;
|
|
56
|
+
readonly query?: unknown;
|
|
57
|
+
readonly command?: unknown;
|
|
58
|
+
readonly commandName?: unknown;
|
|
59
|
+
readonly args?: unknown;
|
|
60
|
+
readonly categoryId?: unknown;
|
|
61
|
+
readonly actionId?: unknown;
|
|
62
|
+
readonly recordId?: unknown;
|
|
63
|
+
readonly fields?: unknown;
|
|
64
|
+
readonly key?: unknown;
|
|
65
|
+
readonly value?: unknown;
|
|
66
|
+
readonly category?: unknown;
|
|
67
|
+
readonly prefix?: unknown;
|
|
68
|
+
readonly includeHidden?: unknown;
|
|
69
|
+
readonly includeParameters?: unknown;
|
|
70
|
+
readonly limit?: unknown;
|
|
71
|
+
readonly confirm?: unknown;
|
|
72
|
+
readonly explicitUserRequest?: unknown;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface AgentHarnessToolDeps {
|
|
76
|
+
readonly commandRegistry: CommandRegistry;
|
|
77
|
+
readonly commandContext: CommandContext;
|
|
78
|
+
readonly toolRegistry: ToolRegistry;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface WorkspaceEditorContext {
|
|
82
|
+
readonly runtimeStarterTemplates: AgentWorkspaceRuntimeSnapshot['runtimeStarterTemplates'];
|
|
83
|
+
readonly selectedRoutine: AgentWorkspaceLocalLibraryItem | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const MODES: readonly AgentHarnessMode[] = [
|
|
87
|
+
'summary',
|
|
88
|
+
'commands',
|
|
89
|
+
'command',
|
|
90
|
+
'run_command',
|
|
91
|
+
'settings',
|
|
92
|
+
'get_setting',
|
|
93
|
+
'set_setting',
|
|
94
|
+
'reset_setting',
|
|
95
|
+
'workspace',
|
|
96
|
+
'workspace_categories',
|
|
97
|
+
'workspace_actions',
|
|
98
|
+
'workspace_action',
|
|
99
|
+
'run_workspace_action',
|
|
100
|
+
'tools',
|
|
101
|
+
'connected_host',
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
function isMode(value: unknown): value is AgentHarnessMode {
|
|
105
|
+
return typeof value === 'string' && MODES.includes(value as AgentHarnessMode);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readString(value: unknown): string {
|
|
109
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readLimit(value: unknown, fallback: number): number {
|
|
113
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
114
|
+
if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
|
|
115
|
+
return Math.max(1, Math.min(500, Math.trunc(parsed)));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readStringArray(value: unknown): readonly string[] {
|
|
119
|
+
if (!Array.isArray(value)) return [];
|
|
120
|
+
return value.map((entry) => typeof entry === 'string' ? entry : String(entry));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readFieldMap(value: unknown): Readonly<Record<string, string>> {
|
|
124
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return {};
|
|
125
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, typeof entry === 'string' ? entry : String(entry)]));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function output(value: unknown): { readonly success: true; readonly output: string } {
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
output: typeof value === 'string' ? value : JSON.stringify(value, null, 2),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function error(message: string): { readonly success: false; readonly error: string } {
|
|
136
|
+
return { success: false, error: message };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function commandMatches(command: SlashCommand, query: string): boolean {
|
|
140
|
+
if (!query) return true;
|
|
141
|
+
const haystack = [
|
|
142
|
+
command.name,
|
|
143
|
+
...(command.aliases ?? []),
|
|
144
|
+
command.description,
|
|
145
|
+
command.usage ?? '',
|
|
146
|
+
command.argsHint ?? '',
|
|
147
|
+
].join('\n').toLowerCase();
|
|
148
|
+
return haystack.includes(query.toLowerCase());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function describeCommand(command: SlashCommand): Record<string, unknown> {
|
|
152
|
+
return {
|
|
153
|
+
name: command.name,
|
|
154
|
+
slash: `/${command.name}`,
|
|
155
|
+
aliases: command.aliases ?? [],
|
|
156
|
+
description: command.description,
|
|
157
|
+
usage: command.usage ?? '',
|
|
158
|
+
argsHint: command.argsHint ?? command.usage ?? '',
|
|
159
|
+
policy: describeCommandPolicy(command.name),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function allWorkspaceActions(): ReadonlyArray<{
|
|
164
|
+
readonly category: AgentWorkspaceCategory;
|
|
165
|
+
readonly action: AgentWorkspaceAction;
|
|
166
|
+
}> {
|
|
167
|
+
return AGENT_WORKSPACE_CATEGORIES.flatMap((category) => category.actions.map((action) => ({ category, action })));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function describeWorkspaceCategory(category: AgentWorkspaceCategory): Record<string, unknown> {
|
|
171
|
+
return {
|
|
172
|
+
id: category.id,
|
|
173
|
+
group: category.group,
|
|
174
|
+
label: category.label,
|
|
175
|
+
summary: category.summary,
|
|
176
|
+
detail: category.detail,
|
|
177
|
+
actions: category.actions.length,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function describeWorkspaceEditor(editor: AgentWorkspaceLocalEditor): Record<string, unknown> {
|
|
182
|
+
return {
|
|
183
|
+
kind: editor.kind,
|
|
184
|
+
mode: editor.mode,
|
|
185
|
+
title: editor.title,
|
|
186
|
+
message: editor.message,
|
|
187
|
+
fields: editor.fields.map((field) => ({
|
|
188
|
+
id: field.id,
|
|
189
|
+
label: field.label,
|
|
190
|
+
required: field.required,
|
|
191
|
+
multiline: field.multiline,
|
|
192
|
+
hint: field.hint,
|
|
193
|
+
redact: field.redact === true,
|
|
194
|
+
default: field.redact ? '<redacted>' : field.value,
|
|
195
|
+
})),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function selectedRoutineFromArgs(
|
|
200
|
+
snapshot: AgentWorkspaceRuntimeSnapshot,
|
|
201
|
+
args: AgentHarnessToolArgs,
|
|
202
|
+
): AgentWorkspaceLocalLibraryItem | null {
|
|
203
|
+
const fields = readFieldMap(args.fields);
|
|
204
|
+
const routineId = readString(args.recordId) || readString(fields.routineId) || readString(fields.id);
|
|
205
|
+
if (!routineId) return null;
|
|
206
|
+
return snapshot.localRoutines.find((routine) => routine.id === routineId || routine.name.toLowerCase() === routineId.toLowerCase()) ?? null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function buildWorkspaceEditorContext(context: CommandContext, args: AgentHarnessToolArgs): WorkspaceEditorContext {
|
|
210
|
+
try {
|
|
211
|
+
const snapshot = buildAgentWorkspaceRuntimeSnapshot(context);
|
|
212
|
+
return {
|
|
213
|
+
runtimeStarterTemplates: snapshot.runtimeStarterTemplates,
|
|
214
|
+
selectedRoutine: selectedRoutineFromArgs(snapshot, args),
|
|
215
|
+
};
|
|
216
|
+
} catch {
|
|
217
|
+
return {
|
|
218
|
+
runtimeStarterTemplates: [],
|
|
219
|
+
selectedRoutine: null,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function createWorkspaceEditor(
|
|
225
|
+
editorKind: AgentWorkspaceEditorKind,
|
|
226
|
+
editorContext: WorkspaceEditorContext | null,
|
|
227
|
+
): AgentWorkspaceLocalEditor | null {
|
|
228
|
+
return createAgentWorkspaceEditor(editorKind, {
|
|
229
|
+
runtimeStarterTemplates: editorContext?.runtimeStarterTemplates ?? [],
|
|
230
|
+
selectedRoutine: editorKind === 'routine-schedule' ? editorContext?.selectedRoutine ?? null : null,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function describeWorkspaceAction(
|
|
235
|
+
category: AgentWorkspaceCategory,
|
|
236
|
+
action: AgentWorkspaceAction,
|
|
237
|
+
options: { readonly includeEditor?: boolean; readonly editorContext?: WorkspaceEditorContext | null } = {},
|
|
238
|
+
): Record<string, unknown> {
|
|
239
|
+
const editor = options.includeEditor && action.editorKind ? createWorkspaceEditor(action.editorKind, options.editorContext ?? null) : null;
|
|
240
|
+
return {
|
|
241
|
+
id: action.id,
|
|
242
|
+
categoryId: category.id,
|
|
243
|
+
category: category.label,
|
|
244
|
+
group: category.group,
|
|
245
|
+
label: action.label,
|
|
246
|
+
detail: action.detail,
|
|
247
|
+
kind: action.kind,
|
|
248
|
+
safety: action.safety,
|
|
249
|
+
...(action.command ? { command: action.command } : {}),
|
|
250
|
+
...(action.targetCategoryId ? { targetCategoryId: action.targetCategoryId } : {}),
|
|
251
|
+
...(action.editorKind ? { editorKind: action.editorKind } : {}),
|
|
252
|
+
...(action.localKind ? { localKind: action.localKind } : {}),
|
|
253
|
+
...(action.localOperation ? { localOperation: action.localOperation } : {}),
|
|
254
|
+
...(editor ? { editor: describeWorkspaceEditor(editor) } : {}),
|
|
255
|
+
...(action.kind === 'local-selection' || action.kind === 'local-operation' ? {
|
|
256
|
+
modelExecution: describeLocalWorkspaceModelExecution(action),
|
|
257
|
+
} : {}),
|
|
258
|
+
...(action.kind === 'editor' && action.editorKind && !isAgentWorkspaceCommandEditorKind(action.editorKind) ? {
|
|
259
|
+
modelExecution: localEditorModelExecution(action.editorKind),
|
|
260
|
+
} : {}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function localEditorModelExecution(editorKind: AgentWorkspaceEditorKind): string {
|
|
265
|
+
if (editorKind === 'memory') return 'Use agent_local_registry with domain:"memory" for create/update/review/stale/delete.';
|
|
266
|
+
if (editorKind === 'note') return 'Use agent_local_registry with domain:"note" for create/update/review/stale/delete.';
|
|
267
|
+
if (editorKind === 'persona') return 'Use agent_local_registry with domain:"persona" for create/update/use/clear_active/review/stale/delete.';
|
|
268
|
+
if (editorKind === 'skill') return 'Use agent_local_registry with domain:"skill" for create/update/enable/disable/review/stale/delete.';
|
|
269
|
+
if (editorKind === 'routine') return 'Use agent_local_registry with domain:"routine" for create/update/enable/disable/start/review/stale/delete.';
|
|
270
|
+
if (editorKind === 'learned-behavior') return 'run_workspace_action can create the learned behavior from fields.';
|
|
271
|
+
if (editorKind === 'profile') return 'run_workspace_action dispatches the matching /agent-profile create command.';
|
|
272
|
+
return 'Use the command field, editor schema, or a first-class Agent model tool when available.';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function listWorkspaceActions(deps: AgentHarnessToolDeps, args: AgentHarnessToolArgs): readonly Record<string, unknown>[] {
|
|
276
|
+
const query = readString(args.query);
|
|
277
|
+
const categoryId = readString(args.categoryId || args.category);
|
|
278
|
+
const limit = readLimit(args.limit, 200);
|
|
279
|
+
const includeEditor = args.includeParameters === true;
|
|
280
|
+
const editorContext = includeEditor ? buildWorkspaceEditorContext(deps.commandContext, args) : null;
|
|
281
|
+
const source = query
|
|
282
|
+
? searchAgentWorkspaceActions(AGENT_WORKSPACE_CATEGORIES, query).map((result) => ({ category: result.category, action: result.action }))
|
|
283
|
+
: allWorkspaceActions();
|
|
284
|
+
return source
|
|
285
|
+
.filter((entry) => !categoryId || entry.category.id === categoryId)
|
|
286
|
+
.slice(0, limit)
|
|
287
|
+
.map((entry) => describeWorkspaceAction(entry.category, entry.action, { includeEditor, editorContext }));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function findWorkspaceAction(args: AgentHarnessToolArgs): { readonly category: AgentWorkspaceCategory; readonly action: AgentWorkspaceAction } | null {
|
|
291
|
+
const actionId = readString(args.actionId || args.query);
|
|
292
|
+
const categoryId = readString(args.categoryId || args.category);
|
|
293
|
+
if (!actionId) return null;
|
|
294
|
+
return allWorkspaceActions().find((entry) => {
|
|
295
|
+
if (categoryId && entry.category.id !== categoryId) return false;
|
|
296
|
+
return entry.action.id === actionId || entry.action.label.toLowerCase() === actionId.toLowerCase();
|
|
297
|
+
}) ?? null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function listCommands(commandRegistry: CommandRegistry, args: AgentHarnessToolArgs): readonly Record<string, unknown>[] {
|
|
301
|
+
const query = readString(args.query);
|
|
302
|
+
const limit = readLimit(args.limit, 200);
|
|
303
|
+
return commandRegistry.list()
|
|
304
|
+
.filter((command) => commandMatches(command, query))
|
|
305
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
306
|
+
.slice(0, limit)
|
|
307
|
+
.map(describeCommand);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function listTools(toolRegistry: ToolRegistry, args: AgentHarnessToolArgs): readonly Record<string, unknown>[] {
|
|
311
|
+
const query = readString(args.query).toLowerCase();
|
|
312
|
+
const includeParameters = args.includeParameters === true;
|
|
313
|
+
const limit = readLimit(args.limit, 200);
|
|
314
|
+
return toolRegistry.getToolDefinitions()
|
|
315
|
+
.filter((tool) => {
|
|
316
|
+
if (!query) return true;
|
|
317
|
+
return [tool.name, tool.description, ...(tool.sideEffects ?? [])].join('\n').toLowerCase().includes(query);
|
|
318
|
+
})
|
|
319
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
320
|
+
.slice(0, limit)
|
|
321
|
+
.map((tool) => ({
|
|
322
|
+
name: tool.name,
|
|
323
|
+
description: tool.description,
|
|
324
|
+
sideEffects: tool.sideEffects ?? [],
|
|
325
|
+
concurrency: tool.concurrency ?? 'parallel',
|
|
326
|
+
supportsProgress: tool.supportsProgress ?? false,
|
|
327
|
+
supportsStreamingOutput: tool.supportsStreamingOutput ?? false,
|
|
328
|
+
...(includeParameters ? { parameters: tool.parameters } : {}),
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function requireConfirmedAction(args: AgentHarnessToolArgs, action: string): string | null {
|
|
333
|
+
const explicitUserRequest = readString(args.explicitUserRequest);
|
|
334
|
+
if (!explicitUserRequest) return `${action} requires explicitUserRequest with the user's exact request or a short faithful summary.`;
|
|
335
|
+
if (args.confirm !== true) return `${action} requires confirm:true after an explicit user request.`;
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function commandFromArgs(args: AgentHarnessToolArgs): { readonly name: string; readonly args: readonly string[] } | null {
|
|
340
|
+
const rawCommand = readString(args.command);
|
|
341
|
+
if (rawCommand) {
|
|
342
|
+
const parsed = parseSlashCommand(rawCommand);
|
|
343
|
+
if (!parsed.name) return null;
|
|
344
|
+
return {
|
|
345
|
+
name: parsed.name,
|
|
346
|
+
args: parsed.args,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const commandName = readString(args.commandName).replace(/^\//, '');
|
|
350
|
+
if (!commandName) return null;
|
|
351
|
+
const commandArgs = readStringArray(args.args);
|
|
352
|
+
return {
|
|
353
|
+
name: commandName,
|
|
354
|
+
args: commandArgs,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function safeCommandDisplay(name: string): string {
|
|
359
|
+
return `/${name}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function runCommand(deps: AgentHarnessToolDeps, args: AgentHarnessToolArgs): Promise<{ readonly success: boolean; readonly output?: string; readonly error?: string }> {
|
|
363
|
+
const confirmationError = requireConfirmedAction(args, 'Slash command invocation');
|
|
364
|
+
if (confirmationError) return error(confirmationError);
|
|
365
|
+
const parsed = commandFromArgs(args);
|
|
366
|
+
if (!parsed) return error('run_command requires command or commandName.');
|
|
367
|
+
if (!deps.commandRegistry.get(parsed.name)) return error(`Unknown slash command /${parsed.name}. Use mode:"commands" to inspect available commands.`);
|
|
368
|
+
|
|
369
|
+
const printed: string[] = [];
|
|
370
|
+
const toolContext: CommandContext = {
|
|
371
|
+
...deps.commandContext,
|
|
372
|
+
print: (text: string) => {
|
|
373
|
+
printed.push(text);
|
|
374
|
+
},
|
|
375
|
+
renderRequest: () => {},
|
|
376
|
+
executeCommand: async (name: string, commandArgs: string[]) => {
|
|
377
|
+
return deps.commandRegistry.execute(name, commandArgs, toolContext);
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
const handled = await deps.commandRegistry.execute(parsed.name, [...parsed.args], toolContext);
|
|
381
|
+
if (!handled) return error(`Unknown slash command /${parsed.name}.`);
|
|
382
|
+
return output([
|
|
383
|
+
`Command ${safeCommandDisplay(parsed.name)} completed.`,
|
|
384
|
+
printed.length > 0 ? printed.join('\n') : '(no text output)',
|
|
385
|
+
].join('\n'));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function fieldReader(editor: AgentWorkspaceLocalEditor, fields: Readonly<Record<string, string>>): (fieldId: string) => string {
|
|
389
|
+
return (fieldId: string) => fields[fieldId] ?? editor.fields.find((field) => field.id === fieldId)?.value ?? '';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function missingRequiredEditorFields(editor: AgentWorkspaceLocalEditor, fields: Readonly<Record<string, string>>): readonly string[] {
|
|
393
|
+
const readField = fieldReader(editor, fields);
|
|
394
|
+
return editor.fields
|
|
395
|
+
.filter((field) => field.required && !readField(field.id).trim())
|
|
396
|
+
.map((field) => field.id);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function runWorkspaceEditorAction(
|
|
400
|
+
deps: AgentHarnessToolDeps,
|
|
401
|
+
action: AgentWorkspaceAction,
|
|
402
|
+
editor: AgentWorkspaceLocalEditor,
|
|
403
|
+
args: AgentHarnessToolArgs,
|
|
404
|
+
): Promise<{ readonly success: boolean; readonly output?: string; readonly error?: string }> {
|
|
405
|
+
const fields = readFieldMap(args.fields);
|
|
406
|
+
const missing = missingRequiredEditorFields(editor, fields);
|
|
407
|
+
if (missing.length > 0) {
|
|
408
|
+
return output({
|
|
409
|
+
status: 'missing_required_fields',
|
|
410
|
+
missing,
|
|
411
|
+
action: action.id,
|
|
412
|
+
editor: describeWorkspaceEditor(editor),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (editor.kind === 'learned-behavior') {
|
|
417
|
+
const confirmationError = requireConfirmedAction(args, 'Workspace learned-behavior capture');
|
|
418
|
+
if (confirmationError) return error(confirmationError);
|
|
419
|
+
const shellPaths = deps.commandContext.workspace.shellPaths;
|
|
420
|
+
if (!shellPaths) return error('Agent shell paths are unavailable.');
|
|
421
|
+
const readField = fieldReader(editor, fields);
|
|
422
|
+
const target = readField('target').trim().toLowerCase();
|
|
423
|
+
if (target !== 'skill' && target !== 'routine' && target !== 'persona') {
|
|
424
|
+
return error('learned-behavior target must be skill, routine, or persona.');
|
|
425
|
+
}
|
|
426
|
+
const created = createAgentWorkspaceLearnedBehavior(shellPaths, {
|
|
427
|
+
target,
|
|
428
|
+
name: readField('name'),
|
|
429
|
+
description: readField('description'),
|
|
430
|
+
notes: readField('notes'),
|
|
431
|
+
tags: splitList(readField('tags')),
|
|
432
|
+
triggers: splitList(readField('triggers')),
|
|
433
|
+
enable: isAffirmative(readField('enable')),
|
|
434
|
+
});
|
|
435
|
+
return output({
|
|
436
|
+
status: 'created',
|
|
437
|
+
kind: created.kind,
|
|
438
|
+
id: created.id,
|
|
439
|
+
name: created.name,
|
|
440
|
+
policy: 'Agent-local behavior only; no connected-host mutation, default knowledge write, or delegated job was created.',
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (editor.kind === 'profile') {
|
|
445
|
+
const readField = fieldReader(editor, fields);
|
|
446
|
+
const name = readField('name');
|
|
447
|
+
const template = readField('template');
|
|
448
|
+
const parts = ['/agent-profile', 'create', name];
|
|
449
|
+
if (template.trim() && template.trim().toLowerCase() !== 'none') parts.push('--template', template);
|
|
450
|
+
parts.push('--yes');
|
|
451
|
+
return runCommand(deps, {
|
|
452
|
+
...args,
|
|
453
|
+
command: parts.map((part, index) => index < 2 || part.startsWith('--') ? part : JSON.stringify(part)).join(' '),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!isAgentWorkspaceCommandEditorKind(editor.kind)) {
|
|
458
|
+
return output({
|
|
459
|
+
status: 'model_tool_required',
|
|
460
|
+
action: action.id,
|
|
461
|
+
editor: describeWorkspaceEditor(editor),
|
|
462
|
+
modelExecution: localEditorModelExecution(editor.kind),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const submission = buildAgentWorkspaceCommandEditorSubmission(
|
|
467
|
+
editor,
|
|
468
|
+
fieldReader(editor, fields),
|
|
469
|
+
true,
|
|
470
|
+
true,
|
|
471
|
+
);
|
|
472
|
+
if (submission.kind === 'editor') {
|
|
473
|
+
return output({
|
|
474
|
+
status: submission.status,
|
|
475
|
+
action: action.id,
|
|
476
|
+
editor: describeWorkspaceEditor(submission.editor),
|
|
477
|
+
actionResult: submission.actionResult ?? null,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (submission.kind === 'prompt') {
|
|
481
|
+
return output({
|
|
482
|
+
status: submission.status,
|
|
483
|
+
action: action.id,
|
|
484
|
+
prompt: submission.prompt,
|
|
485
|
+
actionResult: submission.actionResult,
|
|
486
|
+
note: 'This workspace action submits a normal main-conversation prompt in the TUI. In model-tool context, use the returned prompt as the conversation task instead of creating a hidden nested turn.',
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return runCommand(deps, {
|
|
490
|
+
...args,
|
|
491
|
+
command: submission.command,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function runWorkspaceAction(
|
|
496
|
+
deps: AgentHarnessToolDeps,
|
|
497
|
+
args: AgentHarnessToolArgs,
|
|
498
|
+
): Promise<{ readonly success: boolean; readonly output?: string; readonly error?: string }> {
|
|
499
|
+
const found = findWorkspaceAction(args);
|
|
500
|
+
if (!found) return error('run_workspace_action requires a valid actionId. Use mode:"workspace_actions" to inspect available actions.');
|
|
501
|
+
const { category, action } = found;
|
|
502
|
+
|
|
503
|
+
if (action.safety === 'blocked') {
|
|
504
|
+
return error(`Workspace action ${action.id} is blocked in Agent: ${action.detail}`);
|
|
505
|
+
}
|
|
506
|
+
if (action.kind === 'guidance') {
|
|
507
|
+
const editorContext = buildWorkspaceEditorContext(deps.commandContext, args);
|
|
508
|
+
return output({
|
|
509
|
+
status: 'guidance',
|
|
510
|
+
action: describeWorkspaceAction(category, action, { includeEditor: true, editorContext }),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
if (action.kind === 'workspace' && action.targetCategoryId) {
|
|
514
|
+
const target = AGENT_WORKSPACE_CATEGORIES.find((entry) => entry.id === action.targetCategoryId);
|
|
515
|
+
return output({
|
|
516
|
+
status: 'workspace_target',
|
|
517
|
+
action: describeWorkspaceAction(category, action),
|
|
518
|
+
targetCategory: target ? describeWorkspaceCategory(target) : action.targetCategoryId,
|
|
519
|
+
targetActions: target ? target.actions.map((entry) => describeWorkspaceAction(target, entry)).slice(0, 40) : [],
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
if (action.kind === 'command' && action.command) {
|
|
523
|
+
if (/<[^>\s]+(?:\s+[^>]*)?>/.test(action.command)) {
|
|
524
|
+
return output({
|
|
525
|
+
status: 'needs_concrete_command',
|
|
526
|
+
action: describeWorkspaceAction(category, action),
|
|
527
|
+
note: 'This workspace action is a command template. Provide concrete values with mode:"run_command" once the exact command is known.',
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return runCommand(deps, { ...args, command: action.command });
|
|
531
|
+
}
|
|
532
|
+
if (action.kind === 'editor' && action.editorKind) {
|
|
533
|
+
const editor = createWorkspaceEditor(action.editorKind, buildWorkspaceEditorContext(deps.commandContext, args));
|
|
534
|
+
if (!editor) return error(`No workspace editor bridge exists for ${action.editorKind}.`);
|
|
535
|
+
return runWorkspaceEditorAction(deps, action, editor, args);
|
|
536
|
+
}
|
|
537
|
+
if (action.kind === 'local-selection' || action.kind === 'local-operation') {
|
|
538
|
+
return runLocalWorkspaceAction(deps, action, args);
|
|
539
|
+
}
|
|
540
|
+
const editorContext = buildWorkspaceEditorContext(deps.commandContext, args);
|
|
541
|
+
return output({
|
|
542
|
+
status: 'no_direct_effect',
|
|
543
|
+
action: describeWorkspaceAction(category, action, { includeEditor: true, editorContext }),
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function connectedHostSummary(context: CommandContext, toolRegistry: ToolRegistry): Record<string, unknown> {
|
|
548
|
+
const shellPaths = context.workspace.shellPaths;
|
|
549
|
+
const homeDirectory = shellPaths?.homeDirectory ?? context.platform.configManager.getHomeDirectory() ?? '';
|
|
550
|
+
const connection = resolveAgentConnectedHostConnection(context.platform.configManager, homeDirectory);
|
|
551
|
+
return {
|
|
552
|
+
baseUrl: connection.baseUrl,
|
|
553
|
+
operatorToken: connection.token ? 'configured' : 'missing',
|
|
554
|
+
tokenPath: connection.tokenPath,
|
|
555
|
+
ownership: 'external-connected-host',
|
|
556
|
+
lifecycle: 'GoodVibes Agent can use public connected-host operator routes, but does not start, stop, restart, install, expose, or mutate the host listener.',
|
|
557
|
+
routeFamilies: connectedHostRouteFamilies(),
|
|
558
|
+
capabilities: connectedHostCapabilityMap(toolRegistry),
|
|
559
|
+
blockedCapabilities: blockedConnectedHostCapabilities(),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
|
|
564
|
+
return {
|
|
565
|
+
definition: {
|
|
566
|
+
name: 'agent_harness',
|
|
567
|
+
description: [
|
|
568
|
+
'Discover and operate the GoodVibes Agent harness from the main conversation.',
|
|
569
|
+
'Use this tool to inspect Agent workspace actions, slash commands with policy metadata, model tools, connected-host capabilities, and Agent settings, or to invoke a workspace action/command through the same in-process command registry the user uses in the TUI.',
|
|
570
|
+
'Discovery modes are read-only. Setting writes, resets, slash command invocation, and workspace action invocation require confirm:true plus explicitUserRequest.',
|
|
571
|
+
'This tool preserves Agent product boundaries: connected-host lifecycle and listener posture stay externally owned, connected-host mode reports allowed and blocked route families, and secret-backed settings store raw values through the secret manager while config receives only a secret reference.',
|
|
572
|
+
].join(' '),
|
|
573
|
+
parameters: {
|
|
574
|
+
type: 'object',
|
|
575
|
+
properties: {
|
|
576
|
+
mode: {
|
|
577
|
+
type: 'string',
|
|
578
|
+
enum: MODES,
|
|
579
|
+
description: 'Harness operation to perform.',
|
|
580
|
+
},
|
|
581
|
+
query: {
|
|
582
|
+
type: 'string',
|
|
583
|
+
description: 'Search text for command, setting, or tool catalogs.',
|
|
584
|
+
},
|
|
585
|
+
command: {
|
|
586
|
+
type: 'string',
|
|
587
|
+
description: 'Full slash command string for mode run_command, for example "/settings get provider.model".',
|
|
588
|
+
},
|
|
589
|
+
commandName: {
|
|
590
|
+
type: 'string',
|
|
591
|
+
description: 'Slash command root without the leading slash for mode command or run_command.',
|
|
592
|
+
},
|
|
593
|
+
args: {
|
|
594
|
+
type: 'array',
|
|
595
|
+
items: { type: 'string' },
|
|
596
|
+
description: 'Slash command argument tokens for mode run_command when commandName is used.',
|
|
597
|
+
},
|
|
598
|
+
categoryId: {
|
|
599
|
+
type: 'string',
|
|
600
|
+
description: 'Agent workspace category id for workspace action filtering.',
|
|
601
|
+
},
|
|
602
|
+
actionId: {
|
|
603
|
+
type: 'string',
|
|
604
|
+
description: 'Agent workspace action id for workspace_action or run_workspace_action.',
|
|
605
|
+
},
|
|
606
|
+
fields: {
|
|
607
|
+
type: 'object',
|
|
608
|
+
additionalProperties: { type: 'string' },
|
|
609
|
+
description: 'Field values for run_workspace_action when the workspace action opens an editor form.',
|
|
610
|
+
},
|
|
611
|
+
recordId: {
|
|
612
|
+
type: 'string',
|
|
613
|
+
description: 'Selected Agent-local record id for selection-based local workspace operations.',
|
|
614
|
+
},
|
|
615
|
+
key: {
|
|
616
|
+
type: 'string',
|
|
617
|
+
description: 'Agent setting key for get_setting, set_setting, or reset_setting.',
|
|
618
|
+
},
|
|
619
|
+
value: {
|
|
620
|
+
anyOf: [
|
|
621
|
+
{ type: 'string' },
|
|
622
|
+
{ type: 'number' },
|
|
623
|
+
{ type: 'boolean' },
|
|
624
|
+
],
|
|
625
|
+
description: 'Setting value for set_setting. Strings, booleans, numbers, and enum strings are accepted.',
|
|
626
|
+
},
|
|
627
|
+
category: {
|
|
628
|
+
type: 'string',
|
|
629
|
+
description: 'Setting category filter such as provider, behavior, tools, ui, tts, permissions, automation, or surfaces.',
|
|
630
|
+
},
|
|
631
|
+
prefix: {
|
|
632
|
+
type: 'string',
|
|
633
|
+
description: 'Setting key prefix filter such as surfaces.slack.',
|
|
634
|
+
},
|
|
635
|
+
includeHidden: {
|
|
636
|
+
type: 'boolean',
|
|
637
|
+
description: 'Include settings hidden from the Agent workspace because they are host-owned or non-Agent lifecycle settings.',
|
|
638
|
+
},
|
|
639
|
+
includeParameters: {
|
|
640
|
+
type: 'boolean',
|
|
641
|
+
description: 'Include model tool JSON schemas in tools mode, or workspace editor field schemas in workspace_actions mode.',
|
|
642
|
+
},
|
|
643
|
+
limit: {
|
|
644
|
+
type: 'number',
|
|
645
|
+
description: 'Maximum catalog entries to return.',
|
|
646
|
+
},
|
|
647
|
+
confirm: {
|
|
648
|
+
type: 'boolean',
|
|
649
|
+
description: 'Required true for set_setting, reset_setting, run_command, and mutating run_workspace_action calls after an explicit user request.',
|
|
650
|
+
},
|
|
651
|
+
explicitUserRequest: {
|
|
652
|
+
type: 'string',
|
|
653
|
+
description: 'Exact user request or faithful short summary authorizing a setting mutation or slash command invocation.',
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
required: ['mode'],
|
|
657
|
+
additionalProperties: false,
|
|
658
|
+
},
|
|
659
|
+
sideEffects: ['state'],
|
|
660
|
+
concurrency: 'serial',
|
|
661
|
+
},
|
|
662
|
+
execute: async (rawArgs) => {
|
|
663
|
+
const args = rawArgs as AgentHarnessToolArgs;
|
|
664
|
+
if (!isMode(args.mode)) return error(`Unknown agent_harness mode: ${String(args.mode)}`);
|
|
665
|
+
try {
|
|
666
|
+
if (args.mode === 'summary') {
|
|
667
|
+
return output({
|
|
668
|
+
commands: deps.commandRegistry.list().length,
|
|
669
|
+
settings: deps.commandContext.platform.configManager.getSchema().length,
|
|
670
|
+
workspaceCategories: AGENT_WORKSPACE_CATEGORIES.length,
|
|
671
|
+
workspaceActions: allWorkspaceActions().length,
|
|
672
|
+
tools: deps.toolRegistry.getToolDefinitions().length,
|
|
673
|
+
modelAccess: {
|
|
674
|
+
slashCommands: 'Use mode:"commands" and mode:"command" to inspect; use mode:"run_command" with confirm:true plus explicitUserRequest to execute.',
|
|
675
|
+
workspace: 'Use mode:"workspace_actions" to list and mode:"workspace_action" for editor schemas; set includeParameters:true on workspace_actions to inline editor schemas.',
|
|
676
|
+
settings: 'Use mode:"settings", mode:"get_setting", mode:"set_setting", and mode:"reset_setting".',
|
|
677
|
+
tools: 'Use mode:"tools" with includeParameters:true to inspect first-class model tool schemas.',
|
|
678
|
+
connectedHost: 'Use mode:"connected_host" for the connected-host capability map and blocked boundaries.',
|
|
679
|
+
},
|
|
680
|
+
settingsPolicy: settingsPolicySummary(),
|
|
681
|
+
connectedHost: connectedHostSummary(deps.commandContext, deps.toolRegistry),
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
if (args.mode === 'commands') {
|
|
685
|
+
const commands = listCommands(deps.commandRegistry, args);
|
|
686
|
+
return output({ commands, returned: commands.length, total: deps.commandRegistry.list().length });
|
|
687
|
+
}
|
|
688
|
+
if (args.mode === 'command') {
|
|
689
|
+
const name = readString(args.commandName).replace(/^\//, '');
|
|
690
|
+
const command = name ? deps.commandRegistry.get(name) : null;
|
|
691
|
+
return command ? output(describeCommand(command)) : error(`Unknown slash command /${name || '<missing>'}.`);
|
|
692
|
+
}
|
|
693
|
+
if (args.mode === 'run_command') return runCommand(deps, args);
|
|
694
|
+
if (args.mode === 'settings') {
|
|
695
|
+
const settings = listHarnessSettings(deps.commandContext.platform.configManager, {
|
|
696
|
+
category: readString(args.category) || undefined,
|
|
697
|
+
prefix: readString(args.prefix) || undefined,
|
|
698
|
+
query: readString(args.query) || undefined,
|
|
699
|
+
includeHidden: args.includeHidden === true,
|
|
700
|
+
limit: readLimit(args.limit, 100),
|
|
701
|
+
});
|
|
702
|
+
return output({ settings, returned: settings.length, policy: settingsPolicySummary() });
|
|
703
|
+
}
|
|
704
|
+
if (args.mode === 'get_setting') {
|
|
705
|
+
const key = readString(args.key);
|
|
706
|
+
const setting = getHarnessSetting(deps.commandContext.platform.configManager, key);
|
|
707
|
+
return setting ? output(setting) : error(`Unknown setting ${key || '<missing>'}.`);
|
|
708
|
+
}
|
|
709
|
+
if (args.mode === 'set_setting') {
|
|
710
|
+
const confirmationError = requireConfirmedAction(args, 'Setting mutation');
|
|
711
|
+
if (confirmationError) return error(confirmationError);
|
|
712
|
+
if (args.value === undefined) return error('set_setting requires value.');
|
|
713
|
+
const key = readString(args.key);
|
|
714
|
+
const result = await setHarnessSetting(
|
|
715
|
+
deps.commandContext.platform.configManager,
|
|
716
|
+
deps.commandContext.platform.secretsManager,
|
|
717
|
+
key,
|
|
718
|
+
args.value,
|
|
719
|
+
);
|
|
720
|
+
return output(result);
|
|
721
|
+
}
|
|
722
|
+
if (args.mode === 'reset_setting') {
|
|
723
|
+
const confirmationError = requireConfirmedAction(args, 'Setting reset');
|
|
724
|
+
if (confirmationError) return error(confirmationError);
|
|
725
|
+
const key = readString(args.key);
|
|
726
|
+
const result = await resetHarnessSetting(
|
|
727
|
+
deps.commandContext.platform.configManager,
|
|
728
|
+
deps.commandContext.platform.secretsManager,
|
|
729
|
+
key,
|
|
730
|
+
);
|
|
731
|
+
return output(result);
|
|
732
|
+
}
|
|
733
|
+
if (args.mode === 'workspace' || args.mode === 'workspace_categories') {
|
|
734
|
+
return output({
|
|
735
|
+
categories: AGENT_WORKSPACE_CATEGORIES.map(describeWorkspaceCategory),
|
|
736
|
+
actions: allWorkspaceActions().length,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
if (args.mode === 'workspace_actions') {
|
|
740
|
+
const actions = listWorkspaceActions(deps, args);
|
|
741
|
+
return output({ actions, returned: actions.length, total: allWorkspaceActions().length });
|
|
742
|
+
}
|
|
743
|
+
if (args.mode === 'workspace_action') {
|
|
744
|
+
const found = findWorkspaceAction(args);
|
|
745
|
+
const editorContext = buildWorkspaceEditorContext(deps.commandContext, args);
|
|
746
|
+
return found
|
|
747
|
+
? output(describeWorkspaceAction(found.category, found.action, { includeEditor: true, editorContext }))
|
|
748
|
+
: error(`Unknown Agent workspace action ${readString(args.actionId || args.query) || '<missing>'}.`);
|
|
749
|
+
}
|
|
750
|
+
if (args.mode === 'run_workspace_action') return runWorkspaceAction(deps, args);
|
|
751
|
+
if (args.mode === 'tools') {
|
|
752
|
+
const tools = listTools(deps.toolRegistry, args);
|
|
753
|
+
return output({ tools, returned: tools.length, total: deps.toolRegistry.getToolDefinitions().length });
|
|
754
|
+
}
|
|
755
|
+
if (args.mode === 'connected_host') return output(connectedHostSummary(deps.commandContext, deps.toolRegistry));
|
|
756
|
+
return error(`Unhandled agent_harness mode: ${args.mode}`);
|
|
757
|
+
} catch (err) {
|
|
758
|
+
return error(formatHarnessError(err));
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export function registerAgentHarnessTool(
|
|
765
|
+
registry: ToolRegistry,
|
|
766
|
+
commandRegistry: CommandRegistry,
|
|
767
|
+
commandContext: CommandContext,
|
|
768
|
+
): void {
|
|
769
|
+
registry.register(createAgentHarnessTool({
|
|
770
|
+
commandRegistry,
|
|
771
|
+
commandContext,
|
|
772
|
+
toolRegistry: registry,
|
|
773
|
+
}));
|
|
774
|
+
}
|