@agntn/harnesses 0.0.1

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist/THIRD-PARTY-LICENSES.md +36 -0
  4. package/dist/_chunks/agents-sync.d.mts +417 -0
  5. package/dist/_chunks/agents-sync.d.mts.map +1 -0
  6. package/dist/_chunks/agents-sync.mjs +2719 -0
  7. package/dist/_chunks/agents-sync.mjs.map +1 -0
  8. package/dist/_chunks/agents.mjs +62 -0
  9. package/dist/_chunks/agents.mjs.map +1 -0
  10. package/dist/_chunks/libs/typebox.mjs +5512 -0
  11. package/dist/_chunks/libs/typebox.mjs.map +1 -0
  12. package/dist/_chunks/mcp-servers.mjs +149 -0
  13. package/dist/_chunks/mcp-servers.mjs.map +1 -0
  14. package/dist/_chunks/mcp.mjs +164 -0
  15. package/dist/_chunks/mcp.mjs.map +1 -0
  16. package/dist/_chunks/mcp2.mjs +17 -0
  17. package/dist/_chunks/mcp2.mjs.map +1 -0
  18. package/dist/_chunks/tool-operations.mjs +361 -0
  19. package/dist/_chunks/tool-operations.mjs.map +1 -0
  20. package/dist/_chunks/tool-schemas.d.mts +94 -0
  21. package/dist/_chunks/tool-schemas.d.mts.map +1 -0
  22. package/dist/_chunks/tool-schemas.mjs +112 -0
  23. package/dist/_chunks/tool-schemas.mjs.map +1 -0
  24. package/dist/_chunks/types.mjs +4 -0
  25. package/dist/_chunks/types.mjs.map +1 -0
  26. package/dist/cli.d.mts +1 -0
  27. package/dist/cli.mjs +350 -0
  28. package/dist/cli.mjs.map +1 -0
  29. package/dist/index.d.mts +300 -0
  30. package/dist/index.d.mts.map +1 -0
  31. package/dist/index.mjs +6 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/mcp.d.mts +15 -0
  34. package/dist/mcp.d.mts.map +1 -0
  35. package/dist/mcp.mjs +2 -0
  36. package/dist/tool-operations.d.mts +222 -0
  37. package/dist/tool-operations.d.mts.map +1 -0
  38. package/dist/tool-operations.mjs +3 -0
  39. package/dist/tool-schemas.d.mts +2 -0
  40. package/dist/tool-schemas.mjs +2 -0
  41. package/package.json +102 -0
  42. package/packages/omp/extensions/AGENTS.md +7 -0
  43. package/packages/omp/extensions/harnesses.ts +253 -0
  44. package/packages/pi/extensions/AGENTS.md +7 -0
  45. package/packages/pi/extensions/harnesses.ts +265 -0
  46. package/packages/shared/AGENTS.md +7 -0
  47. package/packages/shared/tui.ts +346 -0
@@ -0,0 +1,346 @@
1
+ import { stripVTControlCharacters } from "node:util";
2
+
3
+ export const HARNESS_TOOL_LABELS = {
4
+ harnesses_detect: "Harnesses Detect",
5
+ harnesses_info: "Harnesses Info",
6
+ harnesses_models: "Harnesses Models",
7
+ harnesses_run: "Harnesses Run",
8
+ harnesses_mcp_list: "Harnesses MCP List",
9
+ harnesses_mcp_add: "Harnesses MCP Add",
10
+ harnesses_mcp_sync: "Harnesses MCP Sync",
11
+ harnesses_agents_sync: "Harnesses Agents Sync",
12
+ harnesses_mcp_remove: "Harnesses MCP Remove",
13
+ } as const;
14
+
15
+ export type HarnessToolName = keyof typeof HARNESS_TOOL_LABELS;
16
+
17
+ export const HARNESS_TOOL_APPROVALS: Record<HarnessToolName, "read" | "exec" | "write"> = {
18
+ harnesses_detect: "read",
19
+ harnesses_info: "read",
20
+ harnesses_models: "exec",
21
+ harnesses_run: "exec",
22
+ harnesses_mcp_list: "read",
23
+ harnesses_mcp_add: "write",
24
+ harnesses_mcp_sync: "write",
25
+ harnesses_agents_sync: "write",
26
+ harnesses_mcp_remove: "write",
27
+ };
28
+
29
+ export interface StatusTheme {
30
+ fg?(color: string, text: string): string;
31
+ bold?(text: string): string;
32
+ }
33
+
34
+ export interface RenderOptions {
35
+ expanded?: boolean;
36
+ isPartial?: boolean;
37
+ spinnerFrame?: number;
38
+ executionStarted?: boolean;
39
+ }
40
+
41
+ export interface RenderedToolResult {
42
+ content?: ReadonlyArray<unknown>;
43
+ details?: unknown;
44
+ isError?: boolean;
45
+ }
46
+
47
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
48
+ const SUBJECT_WIDTH = 72;
49
+ const FIELD_SCAN_LIMIT = 2048;
50
+ const RESULT_BODY_LIMIT = 16_000;
51
+ const TERMINAL_UNSAFE = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
52
+ const MALFORMED_SURROGATE = /\p{Cs}/gu;
53
+
54
+ function cutAt(text: string, end: number): string {
55
+ const last = text.codePointAt(end - 1);
56
+ const splitsSurrogatePair =
57
+ last !== undefined && (last > 0xffff || (last >= 0xd800 && last <= 0xdbff));
58
+ return text.slice(0, splitsSurrogatePair ? end - 1 : end);
59
+ }
60
+
61
+ function clip(text: string, max: number): string {
62
+ return text.length <= max ? text : `${cutAt(text, max - 1)}…`;
63
+ }
64
+
65
+ function cleanTerminalText(text: string): string {
66
+ return stripVTControlCharacters(text.replaceAll(MALFORMED_SURROGATE, "�"))
67
+ .replace(TERMINAL_UNSAFE, " ")
68
+ .replaceAll(/\p{Zs}+/gu, " ")
69
+ .trim();
70
+ }
71
+
72
+ export function sanitizeTerminalText(value: unknown, max = SUBJECT_WIDTH): string {
73
+ const text = String(value);
74
+ const bounded = text.length > FIELD_SCAN_LIMIT ? `${cutAt(text, FIELD_SCAN_LIMIT - 1)}…` : text;
75
+ return clip(cleanTerminalText(bounded), max);
76
+ }
77
+
78
+ function isRecord(value: unknown): value is Record<string, unknown> {
79
+ return typeof value === "object" && value !== null;
80
+ }
81
+
82
+ function scalar(record: Readonly<Record<string, unknown>>, key: string): string | undefined {
83
+ const value = record[key];
84
+ if (typeof value === "string" && value.length > 0) return value;
85
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
86
+ return undefined;
87
+ }
88
+
89
+ function listLength(record: Readonly<Record<string, unknown>>, key: string): number | undefined {
90
+ const value = record[key];
91
+ return Array.isArray(value) ? value.length : undefined;
92
+ }
93
+
94
+ function resultText(result: RenderedToolResult): string {
95
+ const parts: string[] = [];
96
+ for (const part of result.content ?? []) {
97
+ if (isRecord(part) && typeof part.text === "string") parts.push(part.text);
98
+ }
99
+ return parts.join("\n");
100
+ }
101
+
102
+ function expandedBody(result: RenderedToolResult, theme: StatusTheme): string[] {
103
+ const body = resultText(result);
104
+ if (body.length === 0) return [];
105
+ const truncated = body.length > RESULT_BODY_LIMIT;
106
+ const bounded = truncated ? `${cutAt(body, RESULT_BODY_LIMIT - 1)}…` : body;
107
+ return bounded
108
+ .split(/\r?\n/)
109
+ .map((line) => ` ${paint(theme, "toolOutput", cleanTerminalText(line))}`);
110
+ }
111
+
112
+ type CallDescriptionContext = Readonly<{
113
+ record: Readonly<Record<string, unknown>>;
114
+ id?: string;
115
+ idCount?: number;
116
+ name?: string;
117
+ }>;
118
+
119
+ type CallDescriptionRenderer = (context: CallDescriptionContext) => string | undefined;
120
+
121
+ function sanitizeJoined(parts: readonly (string | undefined)[]): string | undefined {
122
+ const value = parts.filter((part) => part !== undefined).join(" ");
123
+ return value.length > 0 ? sanitizeTerminalText(value) : undefined;
124
+ }
125
+
126
+ function idDescription({ id, idCount }: CallDescriptionContext): string | undefined {
127
+ if (id) return sanitizeTerminalText(id);
128
+ return idCount === undefined ? undefined : `${idCount} harnesses`;
129
+ }
130
+
131
+ function runDescription({ id, record }: CallDescriptionContext): string | undefined {
132
+ return sanitizeJoined([id, scalar(record, "prompt")]);
133
+ }
134
+
135
+ function mutationDescription({ id, name }: CallDescriptionContext): string | undefined {
136
+ return sanitizeJoined([id, name]);
137
+ }
138
+
139
+ function agentsDescription({ id, record }: CallDescriptionContext): string | undefined {
140
+ if (record.check === true) return id ? `${sanitizeTerminalText(id)} check` : "check";
141
+ return id ? sanitizeTerminalText(id) : undefined;
142
+ }
143
+
144
+ const CALL_DESCRIPTIONS: Record<HarnessToolName, CallDescriptionRenderer> = {
145
+ harnesses_detect: () => undefined,
146
+ harnesses_info: idDescription,
147
+ harnesses_models: idDescription,
148
+ harnesses_run: runDescription,
149
+ harnesses_mcp_list: idDescription,
150
+ harnesses_mcp_add: mutationDescription,
151
+ harnesses_mcp_sync: idDescription,
152
+ harnesses_agents_sync: agentsDescription,
153
+ harnesses_mcp_remove: mutationDescription,
154
+ };
155
+
156
+ function callDescription(tool: HarnessToolName, args: unknown): string | undefined {
157
+ const record = isRecord(args) ? args : {};
158
+ return CALL_DESCRIPTIONS[tool]({
159
+ record,
160
+ id: scalar(record, "id"),
161
+ idCount: listLength(record, "id"),
162
+ name: scalar(record, "name"),
163
+ });
164
+ }
165
+
166
+ function paint(theme: StatusTheme, color: string, text: string): string {
167
+ return theme.fg ? theme.fg(color, text) : text;
168
+ }
169
+
170
+ function callIcon(options: RenderOptions | undefined): string {
171
+ if (options?.isPartial === false) return "✓";
172
+ if (options?.spinnerFrame !== undefined) {
173
+ return SPINNER_FRAMES[options.spinnerFrame % SPINNER_FRAMES.length] ?? "⠋";
174
+ }
175
+ return options?.executionStarted === true ? "◌" : "·";
176
+ }
177
+
178
+ export function renderToolCall(
179
+ tool: HarnessToolName,
180
+ args: unknown,
181
+ options: RenderOptions | undefined,
182
+ theme: StatusTheme,
183
+ ): string {
184
+ const icon = callIcon(options);
185
+ const iconColor = options?.isPartial === false ? "success" : "accent";
186
+ const label = HARNESS_TOOL_LABELS[tool];
187
+ const title = theme.bold ? theme.bold(label) : label;
188
+ const parts = [paint(theme, iconColor, icon), paint(theme, "toolTitle", title)];
189
+ const description = callDescription(tool, args);
190
+ if (description) parts.push(paint(theme, "dim", description));
191
+ return parts.join(" ");
192
+ }
193
+
194
+ function failureDescription(details: Readonly<Record<string, unknown>>): string | undefined {
195
+ const error = scalar(details, "error");
196
+ return error ? sanitizeTerminalText(error) : undefined;
197
+ }
198
+
199
+ type ResultDetails = Readonly<Record<string, unknown>>;
200
+ type ResultMetaRenderer = (details: ResultDetails) => string[];
201
+
202
+ function compactStrings(values: readonly (string | undefined)[]): string[] {
203
+ return values.filter((value): value is string => value !== undefined);
204
+ }
205
+
206
+ function detectMeta(details: ResultDetails): string[] {
207
+ const harnesses = Array.isArray(details.harnesses) ? details.harnesses : [];
208
+ const installed = harnesses.filter(
209
+ (harness) => isRecord(harness) && harness.installed === true,
210
+ ).length;
211
+ return [`${installed}/${harnesses.length} installed`];
212
+ }
213
+
214
+ function infoMeta(details: ResultDetails): string[] {
215
+ return compactStrings([scalar(details, "id"), scalar(details, "name")]).map((value) =>
216
+ sanitizeTerminalText(value),
217
+ );
218
+ }
219
+
220
+ function modelsMeta(details: ResultDetails): string[] {
221
+ const id = scalar(details, "id");
222
+ const models = listLength(details, "models");
223
+ return compactStrings([
224
+ id ? sanitizeTerminalText(id) : undefined,
225
+ models === undefined ? undefined : `${models} models`,
226
+ ]);
227
+ }
228
+
229
+ function runMeta(details: ResultDetails): string[] {
230
+ const meta: string[] = [];
231
+ const id = scalar(details, "id");
232
+ if (id) meta.push(sanitizeTerminalText(id));
233
+ if (details.timedOut === true) {
234
+ meta.push("timed out");
235
+ } else if (typeof details.exitCode === "number") {
236
+ meta.push(`exit ${details.exitCode}`);
237
+ }
238
+ return meta;
239
+ }
240
+
241
+ function mcpListMeta(details: ResultDetails): string[] {
242
+ const count = listLength(details, "harnesses");
243
+ return count === undefined ? [] : [`${count} harnesses`];
244
+ }
245
+
246
+ function mutationMeta(details: ResultDetails): string[] {
247
+ const action = scalar(details, "action");
248
+ return action ? [sanitizeTerminalText(action)] : [];
249
+ }
250
+
251
+ function mcpSyncMeta(details: ResultDetails): string[] {
252
+ const servers = listLength(details, "servers");
253
+ const targets = listLength(details, "targets");
254
+ return compactStrings([
255
+ servers === undefined ? undefined : `${servers} servers`,
256
+ targets === undefined ? undefined : `${targets} harnesses`,
257
+ ]);
258
+ }
259
+
260
+ function agentsSyncMeta(details: ResultDetails): string[] {
261
+ const targets = Array.isArray(details.targets) ? details.targets : [];
262
+ const changed = targets.filter(
263
+ (target) => !isRecord(target) || target.action !== "skipped",
264
+ ).length;
265
+ return [`${changed} targets`];
266
+ }
267
+
268
+ const RESULT_META: Record<HarnessToolName, ResultMetaRenderer> = {
269
+ harnesses_detect: detectMeta,
270
+ harnesses_info: infoMeta,
271
+ harnesses_models: modelsMeta,
272
+ harnesses_run: runMeta,
273
+ harnesses_mcp_list: mcpListMeta,
274
+ harnesses_mcp_add: mutationMeta,
275
+ harnesses_mcp_sync: mcpSyncMeta,
276
+ harnesses_agents_sync: agentsSyncMeta,
277
+ harnesses_mcp_remove: mutationMeta,
278
+ };
279
+
280
+ function resultMeta(tool: HarnessToolName, details: ResultDetails): string[] {
281
+ return RESULT_META[tool](details);
282
+ }
283
+
284
+ type ResultState = Readonly<{
285
+ noop: boolean;
286
+ failed: boolean;
287
+ error?: string;
288
+ }>;
289
+
290
+ function resultState(
291
+ result: RenderedToolResult,
292
+ details: ResultDetails,
293
+ isError: boolean,
294
+ ): ResultState {
295
+ const noop = scalar(details, "action") === "noop";
296
+ const error = failureDescription(details);
297
+ return {
298
+ noop,
299
+ failed: isError || result.isError === true || error !== undefined || noop,
300
+ ...(error === undefined ? {} : { error }),
301
+ };
302
+ }
303
+
304
+ function statusLabel(tool: HarnessToolName, state: ResultState): string {
305
+ if (state.noop) return "noop";
306
+ if (state.failed) return "failed";
307
+ return HARNESS_TOOL_APPROVALS[tool];
308
+ }
309
+
310
+ function infoBatchSize(tool: HarnessToolName, details: unknown): number | undefined {
311
+ if (tool !== "harnesses_info" || !Array.isArray(details)) return undefined;
312
+ return details.length;
313
+ }
314
+
315
+ function resultSummary(
316
+ tool: HarnessToolName,
317
+ result: RenderedToolResult,
318
+ details: ResultDetails,
319
+ state: ResultState,
320
+ theme: StatusTheme,
321
+ ): string | undefined {
322
+ if (state.error) return paint(theme, "error", state.error);
323
+ if (state.noop) return undefined;
324
+ const batchSize = infoBatchSize(tool, result.details);
325
+ const meta = batchSize === undefined ? resultMeta(tool, details) : [`${batchSize} harnesses`];
326
+ return meta.length > 0 ? paint(theme, "muted", meta.join(" · ")) : undefined;
327
+ }
328
+
329
+ export function renderToolResult(
330
+ tool: HarnessToolName,
331
+ result: RenderedToolResult,
332
+ isError: boolean,
333
+ options: RenderOptions,
334
+ theme: StatusTheme,
335
+ ): string {
336
+ const details = isRecord(result.details) ? result.details : {};
337
+ const state = resultState(result, details, isError);
338
+ const icon = paint(theme, state.failed ? "error" : "success", state.failed ? "✗" : "✓");
339
+ const parts = [icon, paint(theme, "accent", `(${statusLabel(tool, state)})`)];
340
+ const summary = resultSummary(tool, result, details, state, theme);
341
+ if (summary) parts.push(summary);
342
+
343
+ const header = parts.join(" ");
344
+ const body = options.expanded === true ? expandedBody(result, theme) : [];
345
+ return body.length > 0 ? [header, ...body].join("\n") : header;
346
+ }