@pellux/goodvibes-agent 1.0.1 → 1.0.3
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 +14321 -12304
- 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 +101 -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/help.ts +26 -0
- package/src/config/agent-settings-policy.ts +44 -0
- package/src/input/agent-workspace-activation.ts +14 -6
- package/src/input/commands/operator-runtime.ts +139 -3
- package/src/input/settings-modal-agent-policy.ts +5 -44
- package/src/runtime/bootstrap.ts +3 -0
- package/src/tools/agent-harness-cli-metadata.ts +152 -0
- package/src/tools/agent-harness-local-operations.ts +233 -0
- package/src/tools/agent-harness-metadata.ts +408 -0
- package/src/tools/agent-harness-model-tool-catalog.ts +36 -0
- package/src/tools/agent-harness-panel-metadata.ts +121 -0
- package/src/tools/agent-harness-tool.ts +798 -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,152 @@
|
|
|
1
|
+
import type { GoodVibesCliCommand } from '../cli/types.ts';
|
|
2
|
+
import { describeGoodVibesCommandHelp } from '../cli/help.ts';
|
|
3
|
+
import {
|
|
4
|
+
listBlockedGoodVibesCliCommandTokens,
|
|
5
|
+
listGoodVibesCliCommandTokens,
|
|
6
|
+
listGoodVibesCliCommands,
|
|
7
|
+
parseGoodVibesCli,
|
|
8
|
+
} from '../cli/parser.ts';
|
|
9
|
+
import { describeCliCommandPolicy } from './agent-harness-metadata.ts';
|
|
10
|
+
|
|
11
|
+
export interface AgentHarnessCliArgs {
|
|
12
|
+
readonly query?: unknown;
|
|
13
|
+
readonly command?: unknown;
|
|
14
|
+
readonly cliCommand?: unknown;
|
|
15
|
+
readonly commandName?: unknown;
|
|
16
|
+
readonly limit?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readString(value: unknown): string {
|
|
20
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readLimit(value: unknown, fallback: number): number {
|
|
24
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
25
|
+
if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
|
|
26
|
+
return Math.max(1, Math.min(500, Math.trunc(parsed)));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function cliCommandTokens(command: GoodVibesCliCommand): readonly string[] {
|
|
30
|
+
if (command === 'unknown') return [];
|
|
31
|
+
if (command === 'tui') return ['(no command)'];
|
|
32
|
+
return listGoodVibesCliCommandTokens()
|
|
33
|
+
.filter((token) => parseGoodVibesCli([token]).command === command)
|
|
34
|
+
.sort();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fallbackCliSummary(command: GoodVibesCliCommand): string {
|
|
38
|
+
if (command === 'tui') return 'Launch the interactive Agent TUI.';
|
|
39
|
+
if (command === 'help') return 'Print top-level or command-specific help.';
|
|
40
|
+
if (command === 'version') return 'Print the installed Agent package version.';
|
|
41
|
+
return 'Inspect the CLI help for this Agent package command.';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function describeCliCommand(command: GoodVibesCliCommand): Record<string, unknown> {
|
|
45
|
+
const help = describeGoodVibesCommandHelp(command);
|
|
46
|
+
const tokens = cliCommandTokens(command);
|
|
47
|
+
return {
|
|
48
|
+
name: command,
|
|
49
|
+
tokens,
|
|
50
|
+
invocation: command === 'tui' ? 'goodvibes-agent' : `goodvibes-agent ${tokens[0] ?? command}`,
|
|
51
|
+
helpTopic: help?.command ?? command,
|
|
52
|
+
summary: help?.summary ?? fallbackCliSummary(command),
|
|
53
|
+
usage: help?.usage ?? (command === 'tui' ? ['goodvibes-agent [OPTIONS]'] : [`goodvibes-agent ${command} [ARGS]`]),
|
|
54
|
+
aliases: help?.aliases ?? tokens.filter((token) => token !== command),
|
|
55
|
+
subcommands: help?.subcommands ?? [],
|
|
56
|
+
examples: help?.examples ?? [],
|
|
57
|
+
policy: describeCliCommandPolicy(command),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function cliCommandMatches(command: Record<string, unknown>, query: string): boolean {
|
|
62
|
+
if (!query) return true;
|
|
63
|
+
return [
|
|
64
|
+
command.name,
|
|
65
|
+
command.tokens,
|
|
66
|
+
command.summary,
|
|
67
|
+
command.usage,
|
|
68
|
+
command.aliases,
|
|
69
|
+
command.subcommands,
|
|
70
|
+
command.examples,
|
|
71
|
+
command.policy,
|
|
72
|
+
].map((value) => JSON.stringify(value)).join('\n').toLowerCase().includes(query.toLowerCase());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function totalHarnessCliCommands(): number {
|
|
76
|
+
return listGoodVibesCliCommands().filter((command) => command !== 'unknown').length;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function blockedHarnessCliCommandTokens(): readonly string[] {
|
|
80
|
+
return listBlockedGoodVibesCliCommandTokens();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function listHarnessCliCommands(args: AgentHarnessCliArgs): readonly Record<string, unknown>[] {
|
|
84
|
+
const query = readString(args.query);
|
|
85
|
+
const limit = readLimit(args.limit, 200);
|
|
86
|
+
return listGoodVibesCliCommands()
|
|
87
|
+
.filter((command) => command !== 'unknown')
|
|
88
|
+
.map(describeCliCommand)
|
|
89
|
+
.filter((command) => cliCommandMatches(command, query))
|
|
90
|
+
.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
|
91
|
+
.slice(0, limit);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function cliTokensFromArgs(args: AgentHarnessCliArgs): readonly string[] {
|
|
95
|
+
const raw = readString(args.cliCommand) || readString(args.command) || readString(args.commandName) || readString(args.query);
|
|
96
|
+
if (!raw) return [];
|
|
97
|
+
const tokens = raw.split(/\s+/).filter((token) => token.length > 0);
|
|
98
|
+
if (tokens[0] === 'goodvibes-agent' || tokens[0]?.endsWith('/goodvibes-agent')) return tokens.slice(1);
|
|
99
|
+
return tokens;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function describeHarnessCliCommand(args: AgentHarnessCliArgs): Record<string, unknown> {
|
|
103
|
+
const tokens = cliTokensFromArgs(args);
|
|
104
|
+
if (tokens.length === 0) return describeCliCommand('tui');
|
|
105
|
+
const parsed = parseGoodVibesCli(tokens);
|
|
106
|
+
if (parsed.command === 'unknown') {
|
|
107
|
+
return {
|
|
108
|
+
supported: false,
|
|
109
|
+
token: parsed.rawCommand ?? tokens[0],
|
|
110
|
+
errors: parsed.errors,
|
|
111
|
+
blockedTokens: blockedHarnessCliCommandTokens(),
|
|
112
|
+
policy: describeCliCommandPolicy(parsed.rawCommand ?? tokens[0] ?? 'unknown'),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
...describeCliCommand(parsed.command),
|
|
117
|
+
parsed: {
|
|
118
|
+
command: parsed.command,
|
|
119
|
+
rawCommand: parsed.rawCommand,
|
|
120
|
+
commandArgs: parsed.commandArgs,
|
|
121
|
+
positionals: parsed.positionals,
|
|
122
|
+
flags: {
|
|
123
|
+
provider: parsed.flags.provider,
|
|
124
|
+
model: parsed.flags.model,
|
|
125
|
+
agentProfile: parsed.flags.agentProfile,
|
|
126
|
+
runtimeUrl: parsed.flags.runtimeUrl,
|
|
127
|
+
workingDir: parsed.flags.workingDir,
|
|
128
|
+
help: parsed.flags.help,
|
|
129
|
+
version: parsed.flags.version,
|
|
130
|
+
print: parsed.flags.print,
|
|
131
|
+
outputFormat: parsed.flags.outputFormat,
|
|
132
|
+
configOverrides: parsed.flags.configOverrides.map((override) => {
|
|
133
|
+
const index = override.indexOf('=');
|
|
134
|
+
return index < 0 ? override : `${override.slice(0, index)}=<redacted>`;
|
|
135
|
+
}),
|
|
136
|
+
enableFeatures: parsed.flags.enableFeatures,
|
|
137
|
+
disableFeatures: parsed.flags.disableFeatures,
|
|
138
|
+
noAltScreen: parsed.flags.noAltScreen,
|
|
139
|
+
port: parsed.flags.port,
|
|
140
|
+
hostname: parsed.flags.hostname,
|
|
141
|
+
open: parsed.flags.open,
|
|
142
|
+
continueLast: parsed.flags.continueLast,
|
|
143
|
+
resume: parsed.flags.resume,
|
|
144
|
+
session: parsed.flags.session,
|
|
145
|
+
fork: parsed.flags.fork,
|
|
146
|
+
rawOutput: parsed.flags.rawOutput,
|
|
147
|
+
acceptRawOutputRisk: parsed.flags.acceptRawOutputRisk,
|
|
148
|
+
},
|
|
149
|
+
errors: parsed.errors,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
2
|
+
import { AgentNoteRegistry } from '../agent/note-registry.ts';
|
|
3
|
+
import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
|
|
4
|
+
import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
|
|
5
|
+
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
6
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
7
|
+
import {
|
|
8
|
+
createKnowledgeUrlEditorFromNote,
|
|
9
|
+
createMemoryEditorFromNote,
|
|
10
|
+
createMemoryUpdateEditor,
|
|
11
|
+
createNoteUpdateEditor,
|
|
12
|
+
createPersonaEditorFromNote,
|
|
13
|
+
createPersonaUpdateEditor,
|
|
14
|
+
createRoutineEditorFromNote,
|
|
15
|
+
createRoutineUpdateEditor,
|
|
16
|
+
createSkillEditorFromNote,
|
|
17
|
+
createSkillUpdateEditor,
|
|
18
|
+
isAffirmative,
|
|
19
|
+
splitList,
|
|
20
|
+
} from '../input/agent-workspace-editors.ts';
|
|
21
|
+
import type { AgentWorkspaceAction, AgentWorkspaceLocalEditor, AgentWorkspaceLocalOperation } from '../input/agent-workspace-types.ts';
|
|
22
|
+
|
|
23
|
+
export interface AgentHarnessLocalOperationArgs {
|
|
24
|
+
readonly fields?: unknown;
|
|
25
|
+
readonly recordId?: unknown;
|
|
26
|
+
readonly id?: unknown;
|
|
27
|
+
readonly confirm?: unknown;
|
|
28
|
+
readonly explicitUserRequest?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AgentHarnessLocalOperationDeps {
|
|
32
|
+
readonly commandContext: CommandContext;
|
|
33
|
+
readonly toolRegistry: ToolRegistry;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type HarnessResult =
|
|
37
|
+
| { readonly success: true; readonly output: string }
|
|
38
|
+
| { readonly success: false; readonly error: string };
|
|
39
|
+
|
|
40
|
+
type RegistryDomain = 'memory' | 'note' | 'persona' | 'skill' | 'routine';
|
|
41
|
+
type RegistryAction = 'update' | 'review' | 'stale' | 'delete' | 'use' | 'clear_active' | 'enable' | 'disable' | 'start';
|
|
42
|
+
|
|
43
|
+
interface RegistryTarget {
|
|
44
|
+
readonly domain: RegistryDomain;
|
|
45
|
+
readonly action: RegistryAction;
|
|
46
|
+
readonly requiresRecordId: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function output(value: unknown): HarnessResult {
|
|
50
|
+
return { success: true, output: typeof value === 'string' ? value : JSON.stringify(value, null, 2) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function error(message: string): HarnessResult {
|
|
54
|
+
return { success: false, error: message };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readString(value: unknown): string {
|
|
58
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readFieldMap(value: unknown): Readonly<Record<string, string>> {
|
|
62
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return {};
|
|
63
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, typeof entry === 'string' ? entry : String(entry)]));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function describeEditor(editor: AgentWorkspaceLocalEditor): Record<string, unknown> {
|
|
67
|
+
return {
|
|
68
|
+
kind: editor.kind,
|
|
69
|
+
mode: editor.mode,
|
|
70
|
+
recordId: editor.recordId,
|
|
71
|
+
title: editor.title,
|
|
72
|
+
message: editor.message,
|
|
73
|
+
fields: editor.fields.map((field) => ({
|
|
74
|
+
id: field.id,
|
|
75
|
+
label: field.label,
|
|
76
|
+
required: field.required,
|
|
77
|
+
multiline: field.multiline,
|
|
78
|
+
hint: field.hint,
|
|
79
|
+
default: field.redact ? '<redacted>' : field.value,
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readRecordId(args: AgentHarnessLocalOperationArgs): string {
|
|
85
|
+
const fields = readFieldMap(args.fields);
|
|
86
|
+
return readString(args.recordId ?? args.id ?? fields.recordId ?? fields.id);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function requireConfirmed(args: AgentHarnessLocalOperationArgs, label: string): string | null {
|
|
90
|
+
if (!readString(args.explicitUserRequest)) return `${label} requires explicitUserRequest with the user's exact request or a short faithful summary.`;
|
|
91
|
+
if (args.confirm !== true) return `${label} requires confirm:true after an explicit user request.`;
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function fieldReader(editor: AgentWorkspaceLocalEditor, fields: Readonly<Record<string, string>>): (id: string) => string {
|
|
96
|
+
return (id: string) => fields[id] ?? editor.fields.find((field) => field.id === id)?.value ?? '';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function hasExecutionFields(args: AgentHarnessLocalOperationArgs): boolean {
|
|
100
|
+
return Object.keys(readFieldMap(args.fields)).some((key) => key !== 'id' && key !== 'recordId');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function missingRequiredFields(editor: AgentWorkspaceLocalEditor, fields: Readonly<Record<string, string>>): readonly string[] {
|
|
104
|
+
const read = fieldReader(editor, fields);
|
|
105
|
+
return editor.fields.filter((field) => field.required && !read(field.id).trim()).map((field) => field.id);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function registryTarget(operation: AgentWorkspaceLocalOperation): RegistryTarget | null {
|
|
109
|
+
if (operation === 'memory-edit') return { domain: 'memory', action: 'update', requiresRecordId: true };
|
|
110
|
+
if (operation === 'memory-review') return { domain: 'memory', action: 'review', requiresRecordId: true };
|
|
111
|
+
if (operation === 'memory-stale') return { domain: 'memory', action: 'stale', requiresRecordId: true };
|
|
112
|
+
if (operation === 'memory-delete') return { domain: 'memory', action: 'delete', requiresRecordId: true };
|
|
113
|
+
if (operation === 'note-edit') return { domain: 'note', action: 'update', requiresRecordId: true };
|
|
114
|
+
if (operation === 'note-review') return { domain: 'note', action: 'review', requiresRecordId: true };
|
|
115
|
+
if (operation === 'note-stale') return { domain: 'note', action: 'stale', requiresRecordId: true };
|
|
116
|
+
if (operation === 'note-delete') return { domain: 'note', action: 'delete', requiresRecordId: true };
|
|
117
|
+
if (operation === 'persona-edit') return { domain: 'persona', action: 'update', requiresRecordId: true };
|
|
118
|
+
if (operation === 'persona-use') return { domain: 'persona', action: 'use', requiresRecordId: true };
|
|
119
|
+
if (operation === 'persona-review') return { domain: 'persona', action: 'review', requiresRecordId: true };
|
|
120
|
+
if (operation === 'persona-clear') return { domain: 'persona', action: 'clear_active', requiresRecordId: false };
|
|
121
|
+
if (operation === 'persona-delete') return { domain: 'persona', action: 'delete', requiresRecordId: true };
|
|
122
|
+
if (operation === 'skill-edit') return { domain: 'skill', action: 'update', requiresRecordId: true };
|
|
123
|
+
if (operation === 'skill-enable') return { domain: 'skill', action: 'enable', requiresRecordId: true };
|
|
124
|
+
if (operation === 'skill-disable') return { domain: 'skill', action: 'disable', requiresRecordId: true };
|
|
125
|
+
if (operation === 'skill-review') return { domain: 'skill', action: 'review', requiresRecordId: true };
|
|
126
|
+
if (operation === 'skill-delete') return { domain: 'skill', action: 'delete', requiresRecordId: true };
|
|
127
|
+
if (operation === 'routine-edit') return { domain: 'routine', action: 'update', requiresRecordId: true };
|
|
128
|
+
if (operation === 'routine-start') return { domain: 'routine', action: 'start', requiresRecordId: true };
|
|
129
|
+
if (operation === 'routine-enable') return { domain: 'routine', action: 'enable', requiresRecordId: true };
|
|
130
|
+
if (operation === 'routine-disable') return { domain: 'routine', action: 'disable', requiresRecordId: true };
|
|
131
|
+
if (operation === 'routine-review') return { domain: 'routine', action: 'review', requiresRecordId: true };
|
|
132
|
+
if (operation === 'routine-delete') return { domain: 'routine', action: 'delete', requiresRecordId: true };
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function editorForOperation(context: CommandContext, operation: AgentWorkspaceLocalOperation, recordId: string): AgentWorkspaceLocalEditor | null {
|
|
137
|
+
const shellPaths = context.workspace.shellPaths;
|
|
138
|
+
if (operation === 'memory-edit') {
|
|
139
|
+
const record = context.clients?.agentKnowledgeApi?.memory?.get(recordId);
|
|
140
|
+
return record ? createMemoryUpdateEditor(record) : null;
|
|
141
|
+
}
|
|
142
|
+
if (!shellPaths) return null;
|
|
143
|
+
if (operation === 'note-edit') {
|
|
144
|
+
const note = AgentNoteRegistry.fromShellPaths(shellPaths).get(recordId);
|
|
145
|
+
return note ? createNoteUpdateEditor(note) : null;
|
|
146
|
+
}
|
|
147
|
+
if (operation === 'note-promote-memory' || operation === 'note-promote-persona' || operation === 'note-promote-skill' || operation === 'note-promote-routine' || operation === 'note-promote-knowledge-url') {
|
|
148
|
+
const note = AgentNoteRegistry.fromShellPaths(shellPaths).get(recordId);
|
|
149
|
+
if (!note) return null;
|
|
150
|
+
if (operation === 'note-promote-memory') return createMemoryEditorFromNote(note);
|
|
151
|
+
if (operation === 'note-promote-persona') return createPersonaEditorFromNote(note);
|
|
152
|
+
if (operation === 'note-promote-skill') return createSkillEditorFromNote(note);
|
|
153
|
+
if (operation === 'note-promote-routine') return createRoutineEditorFromNote(note);
|
|
154
|
+
return note.sourceUrl ? createKnowledgeUrlEditorFromNote(note) : null;
|
|
155
|
+
}
|
|
156
|
+
if (operation === 'persona-edit') {
|
|
157
|
+
const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
|
|
158
|
+
const persona = registry.get(recordId);
|
|
159
|
+
return persona ? createPersonaUpdateEditor(persona, registry.snapshot().activePersonaId === persona.id) : null;
|
|
160
|
+
}
|
|
161
|
+
if (operation === 'skill-edit') {
|
|
162
|
+
const skill = AgentSkillRegistry.fromShellPaths(shellPaths).get(recordId);
|
|
163
|
+
return skill ? createSkillUpdateEditor(skill) : null;
|
|
164
|
+
}
|
|
165
|
+
if (operation === 'routine-edit') {
|
|
166
|
+
const routine = AgentRoutineRegistry.fromShellPaths(shellPaths).get(recordId);
|
|
167
|
+
return routine ? createRoutineUpdateEditor(routine) : null;
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function localRegistryArgsFromEditor(editor: AgentWorkspaceLocalEditor, fields: Readonly<Record<string, string>>, id?: string): Record<string, unknown> {
|
|
173
|
+
const read = fieldReader(editor, fields);
|
|
174
|
+
if (editor.kind === 'memory') return { domain: 'memory', action: editor.mode === 'update' ? 'update' : 'create', id, cls: read('cls'), scope: read('scope'), summary: read('summary'), detail: read('detail'), tags: splitList(read('tags')), confidence: read('confidence'), provenance: editor.recordId ? 'agent-harness-local-operation' : 'agent-harness-note-promotion' };
|
|
175
|
+
if (editor.kind === 'note') return { domain: 'note', action: 'update', id, title: read('title'), body: read('body'), sourceUrl: read('sourceUrl'), tags: splitList(read('tags')), provenance: 'agent-harness-local-operation' };
|
|
176
|
+
if (editor.kind === 'persona') return { domain: 'persona', action: editor.mode === 'update' ? 'update' : 'create', id, name: read('name'), description: read('description'), body: read('body'), tags: splitList(read('tags')), triggers: splitList(read('triggers')), activate: read('activate'), provenance: editor.recordId ? 'agent-harness-local-operation' : 'agent-harness-note-promotion' };
|
|
177
|
+
if (editor.kind === 'skill') return { domain: 'skill', action: editor.mode === 'update' ? 'update' : 'create', id, name: read('name'), description: read('description'), procedure: read('procedure'), tags: splitList(read('tags')), triggers: splitList(read('triggers')), requiresEnv: splitList(read('requiresEnv')), requiresCommands: splitList(read('requiresCommands')), enabled: isAffirmative(read('enabled')), provenance: editor.recordId ? 'agent-harness-local-operation' : 'agent-harness-note-promotion' };
|
|
178
|
+
return { domain: 'routine', action: editor.mode === 'update' ? 'update' : 'create', id, name: read('name'), description: read('description'), steps: read('steps'), tags: splitList(read('tags')), triggers: splitList(read('triggers')), requiresEnv: splitList(read('requiresEnv')), requiresCommands: splitList(read('requiresCommands')), enabled: isAffirmative(read('enabled')), provenance: editor.recordId ? 'agent-harness-local-operation' : 'agent-harness-note-promotion' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function localRegistryArgsForTarget(target: RegistryTarget, args: AgentHarnessLocalOperationArgs, recordId: string): Record<string, unknown> {
|
|
182
|
+
const fields = readFieldMap(args.fields);
|
|
183
|
+
const base = { domain: target.domain, action: target.action, ...(recordId ? { id: recordId } : {}) };
|
|
184
|
+
const edit = target.action === 'update' ? localRegistryArgsFromEditor({ kind: target.domain, mode: 'update', recordId, title: '', selectedFieldIndex: 0, message: '', fields: [] } as AgentWorkspaceLocalEditor, fields, recordId) : {};
|
|
185
|
+
return {
|
|
186
|
+
...base,
|
|
187
|
+
...edit,
|
|
188
|
+
...(target.action === 'stale' && fields.reason ? { reason: fields.reason } : {}),
|
|
189
|
+
...(target.action === 'delete' ? { confirm: args.confirm, explicitUserRequest: readString(args.explicitUserRequest) } : {}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function executeTool(toolRegistry: ToolRegistry, name: string, toolArgs: Record<string, unknown>): Promise<HarnessResult> {
|
|
194
|
+
if (!toolRegistry.has(name)) return output({ status: 'model_tool_required', modelExecution: { tool: name, args: toolArgs } });
|
|
195
|
+
const result = await toolRegistry.execute(`agent-harness-${name}-${Date.now()}`, name, toolArgs) as { readonly success: boolean; readonly output?: string; readonly error?: string };
|
|
196
|
+
if (!result.success) return error(result.error ?? `${name} failed.`);
|
|
197
|
+
return output({ status: 'executed_model_tool', tool: name, output: result.output ?? '' });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function describeLocalWorkspaceModelExecution(action: AgentWorkspaceAction): Record<string, unknown> | null {
|
|
201
|
+
if (action.kind === 'local-selection') return { tool: 'agent_local_registry', domain: action.localKind, actions: ['list', 'search', 'get'], note: 'TUI selection maps to recordId for model-run local-operation actions.' };
|
|
202
|
+
if (!action.localOperation) return null;
|
|
203
|
+
const target = registryTarget(action.localOperation);
|
|
204
|
+
if (target) return { tool: 'agent_local_registry', domain: target.domain, action: target.action, requiresRecordId: target.requiresRecordId };
|
|
205
|
+
if (action.localOperation === 'note-promote-knowledge-url') return { tool: 'agent_knowledge_ingest', sourceKind: 'url', requiresRecordId: true, selectedRecordDomain: 'note' };
|
|
206
|
+
return { tool: 'agent_local_registry', selectedRecordDomain: 'note', action: 'create', requiresRecordId: true };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function runLocalWorkspaceAction(
|
|
210
|
+
deps: AgentHarnessLocalOperationDeps,
|
|
211
|
+
action: AgentWorkspaceAction,
|
|
212
|
+
args: AgentHarnessLocalOperationArgs,
|
|
213
|
+
): Promise<HarnessResult> {
|
|
214
|
+
if (action.kind === 'local-selection') return output({ status: 'local_selection', action: action.id, modelExecution: describeLocalWorkspaceModelExecution(action) });
|
|
215
|
+
const operation = action.localOperation;
|
|
216
|
+
if (!operation) return output({ status: 'local_registry_action', action: action.id, modelExecution: describeLocalWorkspaceModelExecution(action) });
|
|
217
|
+
const target = registryTarget(operation);
|
|
218
|
+
const recordId = readRecordId(args);
|
|
219
|
+
if (target?.requiresRecordId && !recordId) return output({ status: 'needs_record_id', action: action.id, modelExecution: describeLocalWorkspaceModelExecution(action) });
|
|
220
|
+
const editor = recordId ? editorForOperation(deps.commandContext, operation, recordId) : null;
|
|
221
|
+
if (editor && !hasExecutionFields(args) && args.confirm !== true) return output({ status: 'editor', action: action.id, editor: describeEditor(editor), modelExecution: describeLocalWorkspaceModelExecution(action) });
|
|
222
|
+
const confirmationError = requireConfirmed(args, 'Workspace local registry action');
|
|
223
|
+
if (confirmationError) return error(confirmationError);
|
|
224
|
+
if (editor) {
|
|
225
|
+
const fields = readFieldMap(args.fields);
|
|
226
|
+
const missing = missingRequiredFields(editor, fields);
|
|
227
|
+
if (missing.length > 0) return output({ status: 'missing_required_fields', missing, action: action.id, editor: describeEditor(editor) });
|
|
228
|
+
if (editor.kind === 'knowledge-url') return executeTool(deps.toolRegistry, 'agent_knowledge_ingest', { sourceKind: 'url', url: fieldReader(editor, fields)('url'), tags: splitList(fieldReader(editor, fields)('tags')), folderPath: fieldReader(editor, fields)('folder'), confirm: args.confirm, explicitUserRequest: readString(args.explicitUserRequest) });
|
|
229
|
+
return executeTool(deps.toolRegistry, 'agent_local_registry', localRegistryArgsFromEditor(editor, fields, recordId));
|
|
230
|
+
}
|
|
231
|
+
if (!target) return output({ status: 'local_registry_action', action: action.id, modelExecution: describeLocalWorkspaceModelExecution(action) });
|
|
232
|
+
return executeTool(deps.toolRegistry, 'agent_local_registry', localRegistryArgsForTarget(target, args, recordId));
|
|
233
|
+
}
|