@pellux/goodvibes-agent 1.0.31 → 1.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +38 -3
  2. package/README.md +7 -5
  3. package/dist/package/main.js +7346 -2349
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +6 -2
  6. package/docs/connected-host.md +26 -5
  7. package/docs/getting-started.md +29 -9
  8. package/docs/knowledge-artifacts-and-multimodal.md +16 -4
  9. package/docs/project-planning.md +2 -2
  10. package/docs/providers-and-routing.md +3 -2
  11. package/docs/release-and-publishing.md +15 -11
  12. package/docs/tools-and-commands.md +20 -8
  13. package/package.json +8 -3
  14. package/release/live-verification/live-verification.json +148 -0
  15. package/release/live-verification/live-verification.md +187 -0
  16. package/release/performance-snapshot.json +57 -0
  17. package/release/release-notes.md +28 -0
  18. package/release/release-readiness.json +581 -0
  19. package/src/cli/agent-knowledge-command.ts +5 -5
  20. package/src/cli/agent-knowledge-format.ts +11 -0
  21. package/src/cli/agent-knowledge-runtime.ts +92 -13
  22. package/src/cli/bundle-command.ts +5 -4
  23. package/src/cli/entrypoint.ts +5 -2
  24. package/src/cli/external-runtime.ts +2 -15
  25. package/src/cli/management.ts +4 -3
  26. package/src/input/commands/guidance-runtime.ts +1 -1
  27. package/src/input/commands/knowledge.ts +2 -2
  28. package/src/runtime/bootstrap.ts +2 -2
  29. package/src/runtime/connected-host-auth.ts +16 -0
  30. package/src/tools/agent-channel-send-tool.ts +5 -7
  31. package/src/tools/agent-harness-channel-metadata.ts +177 -0
  32. package/src/tools/agent-harness-connected-host-status.ts +1 -1
  33. package/src/tools/agent-harness-delegation-posture.ts +216 -0
  34. package/src/tools/agent-harness-keybinding-metadata.ts +57 -22
  35. package/src/tools/agent-harness-mcp-metadata.ts +246 -0
  36. package/src/tools/agent-harness-media-posture.ts +282 -0
  37. package/src/tools/agent-harness-metadata.ts +21 -4
  38. package/src/tools/agent-harness-model-routing.ts +501 -0
  39. package/src/tools/agent-harness-notification-metadata.ts +217 -0
  40. package/src/tools/agent-harness-operator-methods.ts +285 -0
  41. package/src/tools/agent-harness-pairing-posture.ts +265 -0
  42. package/src/tools/agent-harness-provider-account-metadata.ts +203 -0
  43. package/src/tools/agent-harness-release-evidence.ts +364 -0
  44. package/src/tools/agent-harness-release-readiness.ts +298 -0
  45. package/src/tools/agent-harness-security-posture.ts +646 -0
  46. package/src/tools/agent-harness-service-posture.ts +201 -0
  47. package/src/tools/agent-harness-session-metadata.ts +282 -0
  48. package/src/tools/agent-harness-setup-posture.ts +295 -0
  49. package/src/tools/agent-harness-tool-schema.ts +103 -27
  50. package/src/tools/agent-harness-tool.ts +209 -236
  51. package/src/tools/agent-harness-ui-surface-metadata.ts +1 -1
  52. package/src/tools/agent-harness-workspace-actions.ts +232 -0
  53. package/src/tools/agent-knowledge-ingest-tool.ts +6 -8
  54. package/src/tools/agent-knowledge-tool.ts +122 -23
  55. package/src/tools/agent-local-registry-tool.ts +4 -5
  56. package/src/tools/agent-media-generate-tool.ts +4 -6
  57. package/src/tools/agent-notify-tool.ts +5 -6
  58. package/src/tools/agent-operator-action-tool.ts +6 -8
  59. package/src/tools/agent-operator-briefing-tool.ts +3 -4
  60. package/src/tools/agent-reminder-schedule-tool.ts +6 -7
  61. package/src/tools/agent-tool-policy-guard.ts +8 -17
  62. package/src/tools/agent-work-plan-tool.ts +3 -4
  63. package/src/version.ts +2 -2
@@ -0,0 +1,217 @@
1
+ import { createHash } from 'node:crypto';
2
+ import type { CommandContext } from '../input/command-registry.ts';
3
+
4
+ export interface AgentHarnessNotificationArgs {
5
+ readonly notificationTargetId?: unknown;
6
+ readonly target?: unknown;
7
+ readonly query?: unknown;
8
+ readonly includeParameters?: unknown;
9
+ readonly limit?: unknown;
10
+ }
11
+
12
+ interface NotificationTargetDescriptor {
13
+ readonly id: string;
14
+ readonly index: number;
15
+ readonly fingerprint: string;
16
+ readonly validUrl: boolean;
17
+ readonly protocol?: string;
18
+ readonly host?: string;
19
+ readonly pathDepth?: number;
20
+ readonly hasQuery?: boolean;
21
+ }
22
+
23
+ type NotificationTargetLookupSource = 'notificationTargetId' | 'target' | 'query';
24
+
25
+ type NotificationTargetResolution =
26
+ | { readonly status: 'found'; readonly target: Record<string, unknown> }
27
+ | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
28
+ | { readonly status: 'missing_lookup'; readonly usage: string };
29
+
30
+ function readString(value: unknown): string {
31
+ return typeof value === 'string' ? value.trim() : '';
32
+ }
33
+
34
+ function readLimit(value: unknown, fallback: number): number {
35
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
36
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
37
+ return Math.max(1, Math.min(500, Math.trunc(parsed)));
38
+ }
39
+
40
+ function readConfiguredWebhookUrls(context: CommandContext): readonly string[] {
41
+ const notifications = context.platform.configManager.getCategory('notifications') as { readonly webhookUrls?: unknown } | null;
42
+ const urls = Array.isArray(notifications?.webhookUrls) ? notifications.webhookUrls : [];
43
+ return urls
44
+ .filter((url): url is string => typeof url === 'string')
45
+ .map((url) => url.trim())
46
+ .filter((url) => url.length > 0);
47
+ }
48
+
49
+ function fingerprint(value: string): string {
50
+ return createHash('sha256').update(value).digest('hex').slice(0, 12);
51
+ }
52
+
53
+ function pathDepth(pathname: string): number {
54
+ return pathname
55
+ .split('/')
56
+ .map((part) => part.trim())
57
+ .filter(Boolean)
58
+ .length;
59
+ }
60
+
61
+ function describeWebhookUrl(url: string, index: number): NotificationTargetDescriptor {
62
+ const id = `notification-target-${index + 1}`;
63
+ try {
64
+ const parsed = new URL(url);
65
+ return {
66
+ id,
67
+ index: index + 1,
68
+ fingerprint: fingerprint(url),
69
+ validUrl: true,
70
+ protocol: parsed.protocol.replace(/:$/, ''),
71
+ host: parsed.host,
72
+ pathDepth: pathDepth(parsed.pathname),
73
+ hasQuery: parsed.search.length > 0,
74
+ };
75
+ } catch {
76
+ return {
77
+ id,
78
+ index: index + 1,
79
+ fingerprint: fingerprint(url),
80
+ validUrl: false,
81
+ };
82
+ }
83
+ }
84
+
85
+ function notificationTargets(context: CommandContext): readonly NotificationTargetDescriptor[] {
86
+ return readConfiguredWebhookUrls(context).map(describeWebhookUrl);
87
+ }
88
+
89
+ function lookupFromArgs(args: AgentHarnessNotificationArgs): { readonly source: NotificationTargetLookupSource; readonly input: string } | null {
90
+ const notificationTargetId = readString(args.notificationTargetId);
91
+ if (notificationTargetId) return { source: 'notificationTargetId', input: notificationTargetId };
92
+ const target = readString(args.target);
93
+ if (target) return { source: 'target', input: target };
94
+ const query = readString(args.query);
95
+ return query ? { source: 'query', input: query } : null;
96
+ }
97
+
98
+ function targetSearchText(target: NotificationTargetDescriptor): string {
99
+ return [
100
+ target.id,
101
+ String(target.index),
102
+ target.fingerprint,
103
+ target.protocol ?? '',
104
+ target.host ?? '',
105
+ target.validUrl ? 'valid' : 'invalid',
106
+ target.hasQuery ? 'query' : 'no-query',
107
+ ].join('\n').toLowerCase();
108
+ }
109
+
110
+ function describeTargetCandidate(target: NotificationTargetDescriptor): Record<string, unknown> {
111
+ return {
112
+ notificationTargetId: target.id,
113
+ index: target.index,
114
+ fingerprint: target.fingerprint,
115
+ validUrl: target.validUrl,
116
+ ...(target.host ? { host: target.host } : {}),
117
+ };
118
+ }
119
+
120
+ function describeTarget(
121
+ target: NotificationTargetDescriptor,
122
+ options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {},
123
+ ): Record<string, unknown> {
124
+ return {
125
+ id: target.id,
126
+ index: target.index,
127
+ fingerprint: target.fingerprint,
128
+ validUrl: target.validUrl,
129
+ ...(target.protocol ? { protocol: target.protocol } : {}),
130
+ ...(target.host ? { host: target.host } : {}),
131
+ ...(target.pathDepth !== undefined ? { pathDepth: target.pathDepth } : {}),
132
+ ...(target.hasQuery !== undefined ? { hasQuery: target.hasQuery } : {}),
133
+ value: '<redacted>',
134
+ ...(options.lookup ? { lookup: options.lookup } : {}),
135
+ policy: {
136
+ effect: 'read-only',
137
+ values: 'Full webhook URLs are not returned because they can contain bearer tokens or secret path/query values.',
138
+ delivery: 'Use agent_notify only for one explicit, confirmed notification requested by the user.',
139
+ management: 'Use confirmed /notify mirrors only when the user explicitly asks to add, remove, clear, test, or send notification targets.',
140
+ },
141
+ modelAccess: {
142
+ sendTool: 'agent_notify',
143
+ listCommand: '/notify list',
144
+ addCommand: '/notify add <url> --yes',
145
+ removeCommand: '/notify remove <url> --yes',
146
+ clearCommand: '/notify clear --yes',
147
+ testCommand: '/notify test --yes',
148
+ settingsCategory: 'notifications.webhookUrls',
149
+ ...(options.includeParameters ? {
150
+ targetValueRequiredForRemove: true,
151
+ targetValuePolicy: 'Ask the user for the exact webhook URL before removing one target; do not infer it from redacted metadata.',
152
+ } : {}),
153
+ },
154
+ };
155
+ }
156
+
157
+ export function notificationTargetCatalogStatus(context: CommandContext): Record<string, unknown> {
158
+ const targets = notificationTargets(context);
159
+ return {
160
+ modes: ['notifications', 'notification_target'],
161
+ targets: targets.length,
162
+ validTargets: targets.filter((target) => target.validUrl).length,
163
+ invalidTargets: targets.filter((target) => !target.validUrl).length,
164
+ readOnly: true,
165
+ deliveryTool: 'agent_notify',
166
+ values: 'redacted',
167
+ };
168
+ }
169
+
170
+ export function listHarnessNotificationTargets(context: CommandContext, args: AgentHarnessNotificationArgs): Record<string, unknown> {
171
+ const query = readString(args.query).toLowerCase();
172
+ const includeParameters = args.includeParameters === true;
173
+ const targets = notificationTargets(context);
174
+ const filtered = targets
175
+ .filter((target) => !query || targetSearchText(target).includes(query))
176
+ .slice(0, readLimit(args.limit, 100));
177
+ return {
178
+ targets: filtered.map((target) => describeTarget(target, { includeParameters })),
179
+ returned: filtered.length,
180
+ total: targets.length,
181
+ validTargets: targets.filter((target) => target.validUrl).length,
182
+ invalidTargets: targets.filter((target) => !target.validUrl).length,
183
+ policy: 'Read-only notification target catalog. Full webhook values are redacted; sends and target management require explicit confirmation through the owning tool or slash-command mirror.',
184
+ };
185
+ }
186
+
187
+ export function describeHarnessNotificationTarget(
188
+ context: CommandContext,
189
+ args: AgentHarnessNotificationArgs,
190
+ ): NotificationTargetResolution {
191
+ const lookup = lookupFromArgs(args);
192
+ if (!lookup) {
193
+ return {
194
+ status: 'missing_lookup',
195
+ usage: 'notification_target requires notificationTargetId, target, or query. Use mode:"notifications" to inspect configured redacted target refs.',
196
+ };
197
+ }
198
+ const targets = notificationTargets(context);
199
+ const normalized = lookup.input.toLowerCase();
200
+ const exact = targets.find((target) => target.id === lookup.input || String(target.index) === lookup.input || target.fingerprint === lookup.input);
201
+ if (exact) return { status: 'found', target: describeTarget(exact, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'id-or-index' } }) };
202
+ const searched = targets.filter((target) => targetSearchText(target).includes(normalized));
203
+ if (searched.length === 1) {
204
+ return { status: 'found', target: describeTarget(searched[0]!, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }) };
205
+ }
206
+ if (searched.length > 1) {
207
+ return {
208
+ status: 'ambiguous',
209
+ input: lookup.input,
210
+ candidates: searched.slice(0, 8).map(describeTargetCandidate),
211
+ };
212
+ }
213
+ return {
214
+ status: 'missing_lookup',
215
+ usage: `Unknown notification target ${lookup.input}. Use mode:"notifications" to inspect configured redacted target refs.`,
216
+ };
217
+ }
@@ -0,0 +1,285 @@
1
+ import { AGENT_KNOWLEDGE_METHODS } from '../cli/agent-knowledge-methods.ts';
2
+ import { OPERATOR_ACTIONS } from '../agent/operator-actions.ts';
3
+
4
+ export interface AgentHarnessOperatorMethodArgs {
5
+ readonly methodId?: unknown;
6
+ readonly target?: unknown;
7
+ readonly query?: unknown;
8
+ readonly includeParameters?: unknown;
9
+ readonly limit?: unknown;
10
+ }
11
+
12
+ type OperatorMethodEffect =
13
+ | 'read-only-network'
14
+ | 'confirmed-connected-host-state'
15
+ | 'confirmed-agent-knowledge-write';
16
+
17
+ type OperatorMethodLookupSource = 'methodId' | 'target' | 'query';
18
+
19
+ interface OperatorMethodDescriptor {
20
+ readonly id: string;
21
+ readonly label: string;
22
+ readonly route: string;
23
+ readonly effect: OperatorMethodEffect;
24
+ readonly owner: 'connected-host';
25
+ readonly preferredModelTool: string;
26
+ readonly confirmation: string;
27
+ readonly boundary: string;
28
+ readonly parameters?: readonly Record<string, unknown>[];
29
+ }
30
+
31
+ type OperatorMethodResolution =
32
+ | { readonly status: 'found'; readonly method: Record<string, unknown> }
33
+ | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
34
+ | { readonly status: 'missing_lookup'; readonly usage: string };
35
+
36
+ const OPERATOR_BRIEFING_METHODS: readonly OperatorMethodDescriptor[] = [
37
+ {
38
+ id: 'projectPlanning.workPlan.snapshot',
39
+ label: 'Read work-plan snapshot.',
40
+ route: '/api/projects/planning/work-plan',
41
+ effect: 'read-only-network',
42
+ owner: 'connected-host',
43
+ preferredModelTool: 'agent_operator_briefing',
44
+ confirmation: 'Read-only; use the owning first-class tool.',
45
+ boundary: 'Public operator read route only; no work-plan mutation or hidden task creation.',
46
+ },
47
+ {
48
+ id: 'approvals.list',
49
+ label: 'List approval posture.',
50
+ route: '/api/approvals',
51
+ effect: 'read-only-network',
52
+ owner: 'connected-host',
53
+ preferredModelTool: 'agent_operator_briefing',
54
+ confirmation: 'Read-only; use the owning first-class tool.',
55
+ boundary: 'Public operator read route only; approval mutations must use explicit allowlisted actions.',
56
+ },
57
+ {
58
+ id: 'automation.integration.snapshot',
59
+ label: 'Read automation posture.',
60
+ route: '/api/automation',
61
+ effect: 'read-only-network',
62
+ owner: 'connected-host',
63
+ preferredModelTool: 'agent_operator_briefing',
64
+ confirmation: 'Read-only; use the owning first-class tool.',
65
+ boundary: 'Public operator read route only; automation definition creation and lifecycle ownership stay blocked.',
66
+ },
67
+ {
68
+ id: 'schedules.list',
69
+ label: 'List connected schedules.',
70
+ route: '/api/automation/schedules',
71
+ effect: 'read-only-network',
72
+ owner: 'connected-host',
73
+ preferredModelTool: 'agent_operator_briefing',
74
+ confirmation: 'Read-only; use the owning first-class tool.',
75
+ boundary: 'Public operator read route only; schedule creation/run requires explicit user intent through allowed tools.',
76
+ },
77
+ {
78
+ id: 'scheduler.capacity',
79
+ label: 'Read scheduler capacity.',
80
+ route: '/api/runtime/scheduler',
81
+ effect: 'read-only-network',
82
+ owner: 'connected-host',
83
+ preferredModelTool: 'agent_operator_briefing',
84
+ confirmation: 'Read-only; use the owning first-class tool.',
85
+ boundary: 'Public operator read route only; no local scheduler or host lifecycle mutation.',
86
+ },
87
+ ];
88
+
89
+ const AGENT_KNOWLEDGE_READ_KEYS = new Set([
90
+ 'status',
91
+ 'ask',
92
+ 'search',
93
+ 'sourcesList',
94
+ 'nodesList',
95
+ 'issuesList',
96
+ 'itemGet',
97
+ 'map',
98
+ 'connectorsList',
99
+ 'connectorGet',
100
+ 'connectorDoctor',
101
+ ]);
102
+
103
+ function readString(value: unknown): string {
104
+ return typeof value === 'string' ? value.trim() : '';
105
+ }
106
+
107
+ function readLimit(value: unknown, fallback: number): number {
108
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
109
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
110
+ return Math.max(1, Math.min(500, Math.trunc(parsed)));
111
+ }
112
+
113
+ function agentKnowledgeMethods(): readonly OperatorMethodDescriptor[] {
114
+ return Object.entries(AGENT_KNOWLEDGE_METHODS).map(([key, method]) => {
115
+ const readOnly = AGENT_KNOWLEDGE_READ_KEYS.has(key);
116
+ return {
117
+ id: method.kind,
118
+ label: readOnly ? `Read isolated Agent Knowledge ${key}.` : `Mutate isolated Agent Knowledge ${key}.`,
119
+ route: method.route,
120
+ effect: readOnly ? 'read-only-network' : 'confirmed-agent-knowledge-write',
121
+ owner: 'connected-host',
122
+ preferredModelTool: readOnly ? 'agent_knowledge' : key === 'reindex' ? 'agent_harness run_command' : 'agent_knowledge_ingest',
123
+ confirmation: readOnly ? 'Read-only; use the owning first-class tool.' : 'Requires explicit user request and confirmation through the owning tool or command bridge.',
124
+ boundary: 'Isolated Agent Knowledge route family only; default knowledge and non-Agent knowledge segments are forbidden.',
125
+ ...(readOnly ? {} : {
126
+ parameters: [
127
+ { name: 'confirm', required: true },
128
+ { name: 'explicitUserRequest', required: true },
129
+ ],
130
+ }),
131
+ } satisfies OperatorMethodDescriptor;
132
+ });
133
+ }
134
+
135
+ function operatorActionMethods(): readonly OperatorMethodDescriptor[] {
136
+ return Object.values(OPERATOR_ACTIONS).map((action) => ({
137
+ id: action.action,
138
+ label: action.label,
139
+ route: action.pathTemplate,
140
+ effect: 'confirmed-connected-host-state',
141
+ owner: 'connected-host',
142
+ preferredModelTool: 'agent_operator_action',
143
+ confirmation: 'Requires confirm:true and explicitUserRequest for the exact target.',
144
+ boundary: 'Allowlisted public operator action only; no arbitrary route invocation, automation definition creation, or host lifecycle control.',
145
+ parameters: [
146
+ { name: action.targetField, required: true },
147
+ { name: 'confirm', required: true },
148
+ { name: 'explicitUserRequest', required: true },
149
+ ],
150
+ }));
151
+ }
152
+
153
+ function allOperatorMethods(): readonly OperatorMethodDescriptor[] {
154
+ return [
155
+ ...OPERATOR_BRIEFING_METHODS,
156
+ ...operatorActionMethods(),
157
+ ...agentKnowledgeMethods(),
158
+ {
159
+ id: 'schedules.create',
160
+ label: 'Create one connected reminder or routine schedule.',
161
+ route: '/api/automation/schedules',
162
+ effect: 'confirmed-connected-host-state',
163
+ owner: 'connected-host',
164
+ preferredModelTool: 'agent_reminder_schedule or agent_harness run_workspace_action',
165
+ confirmation: 'Requires explicit user request and confirmation.',
166
+ boundary: 'Connected schedule creation only; no hidden local scheduler or separate Agent job.',
167
+ parameters: [
168
+ { name: 'scheduleKind', required: true },
169
+ { name: 'scheduleValue', required: true },
170
+ { name: 'message', required: true },
171
+ { name: 'confirm', required: true },
172
+ { name: 'explicitUserRequest', required: true },
173
+ ],
174
+ } satisfies OperatorMethodDescriptor,
175
+ ].sort((a, b) => a.id.localeCompare(b.id));
176
+ }
177
+
178
+ function methodSearchText(method: OperatorMethodDescriptor): string {
179
+ return [
180
+ method.id,
181
+ method.label,
182
+ method.route,
183
+ method.effect,
184
+ method.preferredModelTool,
185
+ method.boundary,
186
+ ].join('\n').toLowerCase();
187
+ }
188
+
189
+ function describeMethod(
190
+ method: OperatorMethodDescriptor,
191
+ options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {},
192
+ ): Record<string, unknown> {
193
+ return {
194
+ id: method.id,
195
+ label: method.label,
196
+ route: method.route,
197
+ effect: method.effect,
198
+ owner: method.owner,
199
+ preferredModelTool: method.preferredModelTool,
200
+ confirmation: method.confirmation,
201
+ boundary: method.boundary,
202
+ ...(options.lookup ? { lookup: options.lookup } : {}),
203
+ ...(options.includeParameters ? { parameters: method.parameters ?? [] } : {}),
204
+ };
205
+ }
206
+
207
+ function describeCandidate(method: OperatorMethodDescriptor): Record<string, unknown> {
208
+ return {
209
+ methodId: method.id,
210
+ route: method.route,
211
+ effect: method.effect,
212
+ preferredModelTool: method.preferredModelTool,
213
+ };
214
+ }
215
+
216
+ function lookupFromArgs(args: AgentHarnessOperatorMethodArgs): { readonly source: OperatorMethodLookupSource; readonly input: string } | null {
217
+ const methodId = readString(args.methodId);
218
+ if (methodId) return { source: 'methodId', input: methodId };
219
+ const target = readString(args.target);
220
+ if (target) return { source: 'target', input: target };
221
+ const query = readString(args.query);
222
+ return query ? { source: 'query', input: query } : null;
223
+ }
224
+
225
+ export function operatorMethodCatalogStatus(): Record<string, unknown> {
226
+ const methods = allOperatorMethods();
227
+ return {
228
+ modes: ['operator_methods', 'operator_method'],
229
+ methods: methods.length,
230
+ readOnlyMethods: methods.filter((method) => method.effect === 'read-only-network').length,
231
+ confirmedMethods: methods.filter((method) => method.effect !== 'read-only-network').length,
232
+ policy: 'This is a read-only catalog. Use the preferred first-class model tool for execution; arbitrary connected-host route invocation remains unavailable.',
233
+ };
234
+ }
235
+
236
+ export function operatorMethodSummary(args: AgentHarnessOperatorMethodArgs): Record<string, unknown> {
237
+ const query = readString(args.query).toLowerCase();
238
+ const limit = readLimit(args.limit, 200);
239
+ const includeParameters = args.includeParameters === true;
240
+ const methods = allOperatorMethods()
241
+ .filter((method) => !query || methodSearchText(method).includes(query))
242
+ .slice(0, limit)
243
+ .map((method) => describeMethod(method, { includeParameters }));
244
+ return {
245
+ methods,
246
+ returned: methods.length,
247
+ total: allOperatorMethods().length,
248
+ policy: 'Read-only operator method catalog. Execute only through the listed preferred first-class tool and only when its confirmation policy is satisfied.',
249
+ };
250
+ }
251
+
252
+ export function describeHarnessOperatorMethod(args: AgentHarnessOperatorMethodArgs): OperatorMethodResolution {
253
+ const lookup = lookupFromArgs(args);
254
+ if (!lookup) {
255
+ return {
256
+ status: 'missing_lookup',
257
+ usage: 'operator_method requires methodId, target, or query.',
258
+ };
259
+ }
260
+ const methods = allOperatorMethods();
261
+ const normalized = lookup.input.toLowerCase();
262
+ const exact = methods.find((method) => method.id === lookup.input);
263
+ if (exact) {
264
+ return { status: 'found', method: describeMethod(exact, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'id' } }) };
265
+ }
266
+ const insensitive = methods.find((method) => method.id.toLowerCase() === normalized);
267
+ if (insensitive) {
268
+ return { status: 'found', method: describeMethod(insensitive, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } }) };
269
+ }
270
+ const searched = methods.filter((method) => methodSearchText(method).includes(normalized));
271
+ if (searched.length === 1) {
272
+ return { status: 'found', method: describeMethod(searched[0]!, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }) };
273
+ }
274
+ if (searched.length > 1) {
275
+ return {
276
+ status: 'ambiguous',
277
+ input: lookup.input,
278
+ candidates: searched.slice(0, 8).map(describeCandidate),
279
+ };
280
+ }
281
+ return {
282
+ status: 'missing_lookup',
283
+ usage: `Unknown operator method ${lookup.input}. Use mode:"operator_methods" to inspect available methods.`,
284
+ };
285
+ }