@agntn/harnesses 0.0.1 → 0.1.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.
- package/README.md +45 -23
- package/dist/_chunks/agents-sync.d.mts +41 -10
- package/dist/_chunks/agents-sync.d.mts.map +1 -1
- package/dist/_chunks/agents-sync.mjs +457 -78
- package/dist/_chunks/agents-sync.mjs.map +1 -1
- package/dist/_chunks/mcp.mjs +7 -5
- package/dist/_chunks/mcp.mjs.map +1 -1
- package/dist/_chunks/tool-operations.mjs +73 -15
- package/dist/_chunks/tool-operations.mjs.map +1 -1
- package/dist/_chunks/types.mjs +1 -1
- package/dist/_chunks/types.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/mcp.d.mts.map +1 -1
- package/dist/tool-operations.d.mts +12 -2
- package/dist/tool-operations.d.mts.map +1 -1
- package/package.json +2 -1
- package/packages/omp/extensions/harnesses.ts +14 -8
- package/packages/pi/extensions/harnesses.ts +30 -8
- package/packages/shared/tui.ts +4 -2
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
import { addAbortListener } from "node:events";
|
|
1
2
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, renameSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
4
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
4
6
|
import os, { homedir } from "node:os";
|
|
5
7
|
import { stripVTControlCharacters } from "node:util";
|
|
6
8
|
import { parse } from "smol-toml";
|
|
9
|
+
const PATH_MARKERS = /^~(?=\/|$)|\$\{HOME\}|\$\{PROJECT_ROOT\}|%([^%]+)%/g;
|
|
7
10
|
function resolvePathTemplate(template, options = {}) {
|
|
8
11
|
const homeDir = options.homeDir ?? os.homedir();
|
|
9
12
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
10
|
-
return template.
|
|
13
|
+
return template.replaceAll(PATH_MARKERS, (match, name) => {
|
|
14
|
+
if (match === "${PROJECT_ROOT}") return projectRoot;
|
|
15
|
+
if (name === void 0) return homeDir;
|
|
16
|
+
return process.env[name] ?? match;
|
|
17
|
+
});
|
|
11
18
|
}
|
|
12
19
|
function agntnConfigDir(options = {}) {
|
|
13
20
|
const base = process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME !== "" ? process.env.XDG_CONFIG_HOME : join(options.homeDir ?? os.homedir(), ".config");
|
|
@@ -53,13 +60,75 @@ function invocationTemplate(invocation, mode) {
|
|
|
53
60
|
case "agentStructured": return invocation.jsonArgs;
|
|
54
61
|
}
|
|
55
62
|
}
|
|
63
|
+
function invocationRetryHint(invocation, mode, options) {
|
|
64
|
+
const withoutStructured = requestedInvocationMode({
|
|
65
|
+
...options,
|
|
66
|
+
structured: false
|
|
67
|
+
});
|
|
68
|
+
if (options.structured === true) {
|
|
69
|
+
if (invocationTemplate(invocation, withoutStructured)) return "; retry with structured: false";
|
|
70
|
+
const alternateWithoutStructured = ALTERNATE_INVOCATION_MODE[withoutStructured];
|
|
71
|
+
if (alternateWithoutStructured !== void 0 && invocationTemplate(invocation, alternateWithoutStructured)) return `; retry with structured: false and tools: ${!options.tools}`;
|
|
72
|
+
}
|
|
73
|
+
const alternateMode = ALTERNATE_INVOCATION_MODE[mode];
|
|
74
|
+
return (alternateMode === void 0 ? void 0 : invocationTemplate(invocation, alternateMode)) ? INVOCATION_RETRY_HINT[mode] ?? "" : "";
|
|
75
|
+
}
|
|
76
|
+
function compareVersions(a, b) {
|
|
77
|
+
const [aBase = "", aPre] = a.split("-", 2);
|
|
78
|
+
const [bBase = "", bPre] = b.split("-", 2);
|
|
79
|
+
const left = aBase.split(".").map(Number);
|
|
80
|
+
const right = bBase.split(".").map(Number);
|
|
81
|
+
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
82
|
+
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
83
|
+
if (diff !== 0) return diff;
|
|
84
|
+
}
|
|
85
|
+
return Number(aPre === void 0) - Number(bPre === void 0);
|
|
86
|
+
}
|
|
56
87
|
function buildInvocationArgs(template, prompt, modelArgs, model) {
|
|
57
|
-
const args = template.map((arg) => arg.replaceAll("{prompt}", prompt));
|
|
58
|
-
if (model !== void 0 && modelArgs) args.push(...modelArgs.map((arg) => arg.replaceAll("{model}", model)));
|
|
88
|
+
const args = template.map((arg) => arg.replaceAll("{prompt}", () => prompt));
|
|
89
|
+
if (model !== void 0 && modelArgs) args.push(...modelArgs.map((arg) => arg.replaceAll("{model}", () => model)));
|
|
59
90
|
return args;
|
|
60
91
|
}
|
|
92
|
+
function signalProcessGroup(pid, signal) {
|
|
93
|
+
try {
|
|
94
|
+
process.kill(-pid, signal);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function terminateCommand(pid) {
|
|
100
|
+
if (process.platform === "win32") {
|
|
101
|
+
await new Promise((resolve, reject) => {
|
|
102
|
+
execFile(join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"), [
|
|
103
|
+
"/PID",
|
|
104
|
+
String(pid),
|
|
105
|
+
"/T",
|
|
106
|
+
"/F"
|
|
107
|
+
], {
|
|
108
|
+
windowsHide: true,
|
|
109
|
+
timeout: 2e3,
|
|
110
|
+
killSignal: "SIGKILL"
|
|
111
|
+
}, (error) => error ? reject(error) : resolve());
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
signalProcessGroup(pid, "SIGTERM");
|
|
116
|
+
await setTimeout$1(500);
|
|
117
|
+
signalProcessGroup(pid, "SIGKILL");
|
|
118
|
+
}
|
|
61
119
|
function executeCommand(command, args, options) {
|
|
62
120
|
return new Promise((resolve, reject) => {
|
|
121
|
+
let stdout = "";
|
|
122
|
+
let stderr = "";
|
|
123
|
+
let stopped;
|
|
124
|
+
let settled = false;
|
|
125
|
+
let timer;
|
|
126
|
+
let abortListener;
|
|
127
|
+
if (options.signal?.aborted) {
|
|
128
|
+
stopped = "abort";
|
|
129
|
+
finish(null);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
63
132
|
const child = spawn(command, args, {
|
|
64
133
|
cwd: options.cwd,
|
|
65
134
|
env: options.env ? {
|
|
@@ -70,36 +139,60 @@ function executeCommand(command, args, options) {
|
|
|
70
139
|
"ignore",
|
|
71
140
|
"pipe",
|
|
72
141
|
"pipe"
|
|
73
|
-
]
|
|
142
|
+
],
|
|
143
|
+
detached: process.platform !== "win32" && (Boolean(options.timeoutMs) || options.signal !== void 0)
|
|
74
144
|
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
let timedOut = false;
|
|
78
|
-
const timer = options.timeoutMs ? setTimeout(() => {
|
|
79
|
-
timedOut = true;
|
|
80
|
-
child.kill("SIGTERM");
|
|
81
|
-
}, options.timeoutMs) : void 0;
|
|
145
|
+
if (options.timeoutMs) timer = setTimeout(() => stop("timeout"), options.timeoutMs);
|
|
146
|
+
if (options.signal) abortListener = addAbortListener(options.signal, () => stop("abort"));
|
|
82
147
|
child.stdout.on("data", (chunk) => {
|
|
83
148
|
stdout += chunk.toString("utf8");
|
|
84
149
|
});
|
|
85
150
|
child.stderr.on("data", (chunk) => {
|
|
86
151
|
stderr += chunk.toString("utf8");
|
|
87
152
|
});
|
|
88
|
-
child.on("error",
|
|
89
|
-
if (timer) clearTimeout(timer);
|
|
90
|
-
reject(error);
|
|
91
|
-
});
|
|
153
|
+
child.on("error", fail);
|
|
92
154
|
child.on("close", (code) => {
|
|
155
|
+
if (stopped === void 0) finish(code);
|
|
156
|
+
});
|
|
157
|
+
function cleanup() {
|
|
93
158
|
if (timer) clearTimeout(timer);
|
|
159
|
+
abortListener?.[Symbol.dispose]();
|
|
160
|
+
}
|
|
161
|
+
function fail(error) {
|
|
162
|
+
if (settled) return;
|
|
163
|
+
settled = true;
|
|
164
|
+
cleanup();
|
|
165
|
+
reject(error);
|
|
166
|
+
}
|
|
167
|
+
function stop(reason) {
|
|
168
|
+
if (settled || stopped !== void 0) return;
|
|
169
|
+
stopped = reason;
|
|
170
|
+
cleanup();
|
|
171
|
+
if (child.pid === void 0) return;
|
|
172
|
+
terminateCommand(child.pid).then(() => {
|
|
173
|
+
child.stdout.destroy();
|
|
174
|
+
child.stderr.destroy();
|
|
175
|
+
finish(null);
|
|
176
|
+
}, (error) => {
|
|
177
|
+
child.stdout.destroy();
|
|
178
|
+
child.stderr.destroy();
|
|
179
|
+
fail(error);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function finish(code) {
|
|
183
|
+
if (settled) return;
|
|
184
|
+
settled = true;
|
|
185
|
+
cleanup();
|
|
94
186
|
resolve({
|
|
95
187
|
command,
|
|
96
188
|
args: [...args],
|
|
97
189
|
stdout,
|
|
98
190
|
stderr,
|
|
99
|
-
exitCode:
|
|
100
|
-
timedOut
|
|
191
|
+
exitCode: stopped === void 0 ? code : null,
|
|
192
|
+
timedOut: stopped === "timeout",
|
|
193
|
+
aborted: stopped === "abort"
|
|
101
194
|
});
|
|
102
|
-
}
|
|
195
|
+
}
|
|
103
196
|
});
|
|
104
197
|
}
|
|
105
198
|
var Harness = class {
|
|
@@ -168,9 +261,7 @@ var Harness = class {
|
|
|
168
261
|
if (options.model !== void 0 && !this.invocation.modelArgs) return `Harness ${this.id} does not support model selection`;
|
|
169
262
|
const mode = requestedInvocationMode(options);
|
|
170
263
|
if (invocationTemplate(this.invocation, mode)) return null;
|
|
171
|
-
|
|
172
|
-
const retry = (alternateMode === void 0 ? void 0 : invocationTemplate(this.invocation, alternateMode)) ? INVOCATION_RETRY_HINT[mode] ?? "" : "";
|
|
173
|
-
return `Harness ${this.id} has no ${INVOCATION_MODE_DESCRIPTION[mode]} invocation${retry}`;
|
|
264
|
+
return `${`Harness ${this.id} has no ${INVOCATION_MODE_DESCRIPTION[mode]} invocation`}${invocationRetryHint(this.invocation, mode, options)}`;
|
|
174
265
|
}
|
|
175
266
|
invoke(prompt, options = {}) {
|
|
176
267
|
const invocationOptions = {
|
|
@@ -181,8 +272,19 @@ var Harness = class {
|
|
|
181
272
|
};
|
|
182
273
|
const built = this.buildInvocation(prompt, invocationOptions);
|
|
183
274
|
if (!built) return Promise.reject(new Error(this.invocationError(invocationOptions) ?? "Invalid invocation"));
|
|
275
|
+
const versionError = this.readOnlyVersionError(invocationOptions);
|
|
276
|
+
if (versionError) return Promise.reject(new Error(versionError));
|
|
184
277
|
return executeCommand(built.command, built.args, options);
|
|
185
278
|
}
|
|
279
|
+
readOnlyVersionError(options) {
|
|
280
|
+
const floor = this.invocation?.readOnlyMinVersion;
|
|
281
|
+
if (floor === void 0 || options.readOnly !== true) return null;
|
|
282
|
+
const installed = this.version;
|
|
283
|
+
const error = `Harness ${this.id} requires version ${floor} or newer for read-only runs`;
|
|
284
|
+
if (installed === null) return `${error}; installed version unknown`;
|
|
285
|
+
if (compareVersions(installed, floor) < 0) return `${error}; installed ${installed}`;
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
186
288
|
buildModelListInvocation(search) {
|
|
187
289
|
if (!this.modelListing) return null;
|
|
188
290
|
const command = this.binaries[0];
|
|
@@ -191,7 +293,7 @@ var Harness = class {
|
|
|
191
293
|
if (!template) return null;
|
|
192
294
|
return {
|
|
193
295
|
command,
|
|
194
|
-
args: template.map((arg) => arg.replaceAll("{search}", search ?? ""))
|
|
296
|
+
args: template.map((arg) => arg.replaceAll("{search}", () => search ?? ""))
|
|
195
297
|
};
|
|
196
298
|
}
|
|
197
299
|
async listModels(options = {}) {
|
|
@@ -200,7 +302,7 @@ var Harness = class {
|
|
|
200
302
|
const result = await executeCommand(built.command, built.args, options);
|
|
201
303
|
return {
|
|
202
304
|
...result,
|
|
203
|
-
models: !result.timedOut && result.exitCode === 0 ? this.parseModelListingOutput(result.stdout) : []
|
|
305
|
+
models: !result.timedOut && !result.aborted && result.exitCode === 0 ? this.parseModelListingOutput(result.stdout) : []
|
|
204
306
|
};
|
|
205
307
|
}
|
|
206
308
|
parseModelListingOutput(_stdout) {
|
|
@@ -531,9 +633,26 @@ var Claude = class extends Harness {
|
|
|
531
633
|
"--tools",
|
|
532
634
|
""
|
|
533
635
|
],
|
|
636
|
+
readOnlyArgs: [
|
|
637
|
+
"-p",
|
|
638
|
+
"{prompt}",
|
|
639
|
+
"--strict-mcp-config",
|
|
640
|
+
"--tools",
|
|
641
|
+
"Read,Glob,Grep"
|
|
642
|
+
],
|
|
643
|
+
readOnlyJsonArgs: [
|
|
644
|
+
"-p",
|
|
645
|
+
"--output-format",
|
|
646
|
+
"json",
|
|
647
|
+
"{prompt}",
|
|
648
|
+
"--strict-mcp-config",
|
|
649
|
+
"--tools",
|
|
650
|
+
"Read,Glob,Grep"
|
|
651
|
+
],
|
|
652
|
+
readOnlyMinVersion: "2.1.175",
|
|
534
653
|
modelArgs: ["--model", "{model}"],
|
|
535
654
|
level: "official",
|
|
536
|
-
note: "Headless print mode; add --output-format json for structured output."
|
|
655
|
+
note: "Headless print mode; add --output-format json for structured output. Read-only runs keep the built-in Read, Glob and Grep tools and --strict-mcp-config drops every configured MCP server, so no write tool exists whatever permission mode the settings carry; verified on 2.1.175 and 2.1.268. --permission-mode plan does not qualify: its shell commands run through the auto mode classifier, which let a file write through."
|
|
537
656
|
};
|
|
538
657
|
mcpConfigs = [{
|
|
539
658
|
path: "~/.claude.json",
|
|
@@ -691,20 +810,27 @@ var Codex = class extends Harness {
|
|
|
691
810
|
commands = [];
|
|
692
811
|
hooks = [];
|
|
693
812
|
invocation = {
|
|
694
|
-
args: [
|
|
813
|
+
args: [
|
|
814
|
+
"exec",
|
|
815
|
+
"--skip-git-repo-check",
|
|
816
|
+
"{prompt}"
|
|
817
|
+
],
|
|
695
818
|
jsonArgs: [
|
|
696
819
|
"exec",
|
|
820
|
+
"--skip-git-repo-check",
|
|
697
821
|
"--json",
|
|
698
822
|
"{prompt}"
|
|
699
823
|
],
|
|
700
824
|
readOnlyArgs: [
|
|
701
825
|
"exec",
|
|
826
|
+
"--skip-git-repo-check",
|
|
702
827
|
"--sandbox",
|
|
703
828
|
"read-only",
|
|
704
829
|
"{prompt}"
|
|
705
830
|
],
|
|
706
831
|
readOnlyJsonArgs: [
|
|
707
832
|
"exec",
|
|
833
|
+
"--skip-git-repo-check",
|
|
708
834
|
"--sandbox",
|
|
709
835
|
"read-only",
|
|
710
836
|
"--json",
|
|
@@ -1069,7 +1195,7 @@ var Gemini = class extends Harness {
|
|
|
1069
1195
|
var GitHubCopilot = class extends Harness {
|
|
1070
1196
|
id = "github-copilot";
|
|
1071
1197
|
name = "GitHub Copilot";
|
|
1072
|
-
binaries = [];
|
|
1198
|
+
binaries = ["copilot"];
|
|
1073
1199
|
capabilities = {
|
|
1074
1200
|
mcp: true,
|
|
1075
1201
|
vision: true,
|
|
@@ -1247,9 +1373,24 @@ var Grok = class extends Harness {
|
|
|
1247
1373
|
"--output-format",
|
|
1248
1374
|
"json"
|
|
1249
1375
|
],
|
|
1376
|
+
readOnlyArgs: [
|
|
1377
|
+
"-p",
|
|
1378
|
+
"{prompt}",
|
|
1379
|
+
"--sandbox",
|
|
1380
|
+
"read-only"
|
|
1381
|
+
],
|
|
1382
|
+
readOnlyJsonArgs: [
|
|
1383
|
+
"-p",
|
|
1384
|
+
"{prompt}",
|
|
1385
|
+
"--sandbox",
|
|
1386
|
+
"read-only",
|
|
1387
|
+
"--output-format",
|
|
1388
|
+
"json"
|
|
1389
|
+
],
|
|
1390
|
+
readOnlyMinVersion: "1.0.13",
|
|
1250
1391
|
modelArgs: ["--model", "{model}"],
|
|
1251
1392
|
level: "official",
|
|
1252
|
-
note: "-p is short for --single; add --output-format json for structured output."
|
|
1393
|
+
note: "-p is short for --single; add --output-format json for structured output. --sandbox read-only is kernel-enforced (Landlock, Seatbelt) regardless of the inherited permission mode and still allows writes to ~/.grok and temp dirs; verified on Linux with 1.0.13 and 1.0.25."
|
|
1253
1394
|
};
|
|
1254
1395
|
mcpConfigs = [{
|
|
1255
1396
|
path: "~/.grok/config.toml",
|
|
@@ -1576,21 +1717,9 @@ var Omp = class extends Harness {
|
|
|
1576
1717
|
"json",
|
|
1577
1718
|
"{prompt}"
|
|
1578
1719
|
],
|
|
1579
|
-
noToolsArgs: [
|
|
1580
|
-
"-p",
|
|
1581
|
-
"--no-tools",
|
|
1582
|
-
"{prompt}"
|
|
1583
|
-
],
|
|
1584
|
-
noToolsJsonArgs: [
|
|
1585
|
-
"-p",
|
|
1586
|
-
"--no-tools",
|
|
1587
|
-
"--mode",
|
|
1588
|
-
"json",
|
|
1589
|
-
"{prompt}"
|
|
1590
|
-
],
|
|
1591
1720
|
modelArgs: ["--model={model}"],
|
|
1592
1721
|
level: "official",
|
|
1593
|
-
note: "Add --mode json for structured event output."
|
|
1722
|
+
note: "Add --mode json for structured event output. --no-tools disables only OMP's bundled tools, so it cannot provide an advisor mode without tools."
|
|
1594
1723
|
};
|
|
1595
1724
|
mcpConfigs = [{
|
|
1596
1725
|
path: "~/.omp/agent/mcp.json",
|
|
@@ -1767,16 +1896,37 @@ const MODEL_HEADERS = [
|
|
|
1767
1896
|
"thinking",
|
|
1768
1897
|
"images"
|
|
1769
1898
|
];
|
|
1770
|
-
function parseTokenCount(value) {
|
|
1899
|
+
function parseTokenCount(value, harness) {
|
|
1771
1900
|
const match = /^(\d+(?:\.\d+)?)([KM])?$/.exec(value);
|
|
1772
|
-
if (!match?.[1]) throw new Error(`Invalid
|
|
1901
|
+
if (!match?.[1]) throw new Error(`Invalid ${harness} token count: ${JSON.stringify(value)}`);
|
|
1773
1902
|
const multiplier = match[2] === "M" ? 1e6 : match[2] === "K" ? 1e3 : 1;
|
|
1774
1903
|
return Number(match[1]) * multiplier;
|
|
1775
1904
|
}
|
|
1776
|
-
function parseBoolean(value, column) {
|
|
1905
|
+
function parseBoolean(value, column, harness) {
|
|
1777
1906
|
if (value === "yes") return true;
|
|
1778
1907
|
if (value === "no") return false;
|
|
1779
|
-
throw new Error(`Invalid
|
|
1908
|
+
throw new Error(`Invalid ${harness} ${column} value: ${JSON.stringify(value)}`);
|
|
1909
|
+
}
|
|
1910
|
+
function parsePiModelTable(stdout, harness) {
|
|
1911
|
+
const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1912
|
+
const first = lines[0];
|
|
1913
|
+
if (!first) throw new Error(`Unexpected empty ${harness} model-list output`);
|
|
1914
|
+
if (first.startsWith("No models available.") || first.startsWith("No models matching ")) return [];
|
|
1915
|
+
const headers = first.split(/\s{2,}/);
|
|
1916
|
+
if (headers.length !== MODEL_HEADERS.length || headers.some((value, i) => value !== MODEL_HEADERS[i])) throw new Error(`Unexpected ${harness} model-list header: ${JSON.stringify(first)}`);
|
|
1917
|
+
return lines.slice(1).map((line) => {
|
|
1918
|
+
const columns = line.split(/\s{2,}/);
|
|
1919
|
+
const [provider, id, context, maxOutput, thinking, images] = columns;
|
|
1920
|
+
if (columns.length !== MODEL_HEADERS.length || !provider || !id || !context || !maxOutput || !thinking || !images) throw new Error(`Unexpected ${harness} model-list row: ${JSON.stringify(line)}`);
|
|
1921
|
+
return {
|
|
1922
|
+
provider,
|
|
1923
|
+
id,
|
|
1924
|
+
contextWindow: parseTokenCount(context, harness),
|
|
1925
|
+
maxOutputTokens: parseTokenCount(maxOutput, harness),
|
|
1926
|
+
thinking: parseBoolean(thinking, "thinking", harness),
|
|
1927
|
+
images: parseBoolean(images, "images", harness)
|
|
1928
|
+
};
|
|
1929
|
+
});
|
|
1780
1930
|
}
|
|
1781
1931
|
var Pi = class extends Harness {
|
|
1782
1932
|
id = "pi";
|
|
@@ -1883,6 +2033,20 @@ var Pi = class extends Harness {
|
|
|
1883
2033
|
"json",
|
|
1884
2034
|
"{prompt}"
|
|
1885
2035
|
],
|
|
2036
|
+
readOnlyArgs: [
|
|
2037
|
+
"-p",
|
|
2038
|
+
"--tools",
|
|
2039
|
+
"read,grep,find,ls",
|
|
2040
|
+
"{prompt}"
|
|
2041
|
+
],
|
|
2042
|
+
readOnlyJsonArgs: [
|
|
2043
|
+
"-p",
|
|
2044
|
+
"--tools",
|
|
2045
|
+
"read,grep,find,ls",
|
|
2046
|
+
"--mode",
|
|
2047
|
+
"json",
|
|
2048
|
+
"{prompt}"
|
|
2049
|
+
],
|
|
1886
2050
|
modelArgs: ["--model", "{model}"],
|
|
1887
2051
|
level: "official",
|
|
1888
2052
|
note: "Add --mode json for structured event output."
|
|
@@ -1903,25 +2067,172 @@ var Pi = class extends Harness {
|
|
|
1903
2067
|
projectMarkers: [".pi"]
|
|
1904
2068
|
};
|
|
1905
2069
|
parseModelListingOutput(stdout) {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2070
|
+
return parsePiModelTable(stdout, "Pi");
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
var PrimeAgent = class extends Harness {
|
|
2074
|
+
id = "prime-agent";
|
|
2075
|
+
name = "Prime Agent";
|
|
2076
|
+
binaries = ["prime-agent"];
|
|
2077
|
+
capabilities = {
|
|
2078
|
+
mcp: true,
|
|
2079
|
+
vision: true,
|
|
2080
|
+
audio: false,
|
|
2081
|
+
video: false,
|
|
2082
|
+
tools: true,
|
|
2083
|
+
streaming: true
|
|
2084
|
+
};
|
|
2085
|
+
config = [{
|
|
2086
|
+
path: "~/.prime/agent/settings.json",
|
|
2087
|
+
scope: "user",
|
|
2088
|
+
level: "official",
|
|
2089
|
+
note: "Config dir overridable with PRIME_AGENT_CODING_AGENT_DIR."
|
|
2090
|
+
}, {
|
|
2091
|
+
path: ".prime/agent/settings.json",
|
|
2092
|
+
scope: "project",
|
|
2093
|
+
level: "official"
|
|
2094
|
+
}];
|
|
2095
|
+
sessions = [{
|
|
2096
|
+
path: "~/.prime/agent/sessions/<session-id>.jsonl",
|
|
2097
|
+
scope: "data",
|
|
2098
|
+
level: "official",
|
|
2099
|
+
note: "Flat directory of JSONL transcripts whose header records the working directory; session ids are UUIDv7. Overridable with PRIME_AGENT_SESSION_DIR or --session-dir."
|
|
2100
|
+
}, {
|
|
2101
|
+
path: "~/.prime/agent/session-artifacts/<session-id>/",
|
|
2102
|
+
scope: "data",
|
|
2103
|
+
level: "official",
|
|
2104
|
+
note: "Per-session Python kernel state, scheduled jobs, harness state and child session transcripts under sub-<id>/."
|
|
2105
|
+
}];
|
|
2106
|
+
persistence = [
|
|
2107
|
+
{
|
|
2108
|
+
format: "JSON",
|
|
2109
|
+
level: "official",
|
|
2110
|
+
note: "Settings, auth, telemetry, model cache and session artifact state files."
|
|
2111
|
+
},
|
|
2112
|
+
{
|
|
2113
|
+
format: "JSONL",
|
|
2114
|
+
level: "official",
|
|
2115
|
+
note: "Session transcripts, the RLM spawn ledger and structured logs."
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
format: "dill",
|
|
2119
|
+
level: "official",
|
|
2120
|
+
note: "Pickled Python REPL state (kernel-state.dill) in session artifacts."
|
|
2121
|
+
}
|
|
2122
|
+
];
|
|
2123
|
+
instructions = [
|
|
2124
|
+
{
|
|
2125
|
+
path: "AGENTS.md",
|
|
2126
|
+
scope: "project",
|
|
2127
|
+
level: "official",
|
|
2128
|
+
note: "Loaded from parent directories and cwd; CLAUDE.md accepted as an alternative."
|
|
2129
|
+
},
|
|
2130
|
+
{
|
|
2131
|
+
path: ".prime/agent/SYSTEM.md",
|
|
2132
|
+
scope: "project",
|
|
2133
|
+
level: "official",
|
|
2134
|
+
note: "Replaces the default system prompt; APPEND_SYSTEM.md appends instead."
|
|
2135
|
+
},
|
|
2136
|
+
{
|
|
2137
|
+
path: "~/.prime/agent/AGENTS.md",
|
|
2138
|
+
scope: "user",
|
|
2139
|
+
level: "official",
|
|
2140
|
+
note: "Global user-level instructions; CLAUDE.md accepted as an alternative."
|
|
2141
|
+
},
|
|
2142
|
+
{
|
|
2143
|
+
path: "~/.prime/agent/SYSTEM.md",
|
|
2144
|
+
scope: "user",
|
|
2145
|
+
level: "official",
|
|
2146
|
+
note: "Global system prompt replacement; APPEND_SYSTEM.md appends instead."
|
|
2147
|
+
}
|
|
2148
|
+
];
|
|
2149
|
+
skills = [
|
|
2150
|
+
{
|
|
2151
|
+
path: ".prime/agent/skills/",
|
|
2152
|
+
scope: "project",
|
|
2153
|
+
level: "official"
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
path: ".agents/skills/",
|
|
2157
|
+
scope: "project",
|
|
2158
|
+
level: "official",
|
|
2159
|
+
note: "Also scanned in ancestor directories up to the Git repository root; root .md files are ignored there."
|
|
2160
|
+
},
|
|
2161
|
+
{
|
|
2162
|
+
path: "~/.prime/agent/skills/",
|
|
2163
|
+
scope: "user",
|
|
2164
|
+
level: "official",
|
|
2165
|
+
note: "Global user-level skills."
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
path: "~/.agents/skills/",
|
|
2169
|
+
scope: "user",
|
|
2170
|
+
level: "official",
|
|
2171
|
+
note: "Shared skills directory; root .md files are ignored there."
|
|
2172
|
+
}
|
|
2173
|
+
];
|
|
2174
|
+
commands = [{
|
|
2175
|
+
path: ".prime/agent/prompts/",
|
|
2176
|
+
scope: "project",
|
|
2177
|
+
level: "official",
|
|
2178
|
+
note: "Prompt templates invoked as slash commands."
|
|
2179
|
+
}, {
|
|
2180
|
+
path: "~/.prime/agent/prompts/",
|
|
2181
|
+
scope: "user",
|
|
2182
|
+
level: "official",
|
|
2183
|
+
note: "Global user-level prompt templates."
|
|
2184
|
+
}];
|
|
2185
|
+
hooks = [];
|
|
2186
|
+
invocation = {
|
|
2187
|
+
args: ["-p", "{prompt}"],
|
|
2188
|
+
jsonArgs: [
|
|
2189
|
+
"-p",
|
|
2190
|
+
"--mode",
|
|
2191
|
+
"json",
|
|
2192
|
+
"{prompt}"
|
|
2193
|
+
],
|
|
2194
|
+
noToolsArgs: [
|
|
2195
|
+
"-p",
|
|
2196
|
+
"--no-tools",
|
|
2197
|
+
"{prompt}"
|
|
2198
|
+
],
|
|
2199
|
+
noToolsJsonArgs: [
|
|
2200
|
+
"-p",
|
|
2201
|
+
"--no-tools",
|
|
2202
|
+
"--mode",
|
|
2203
|
+
"json",
|
|
2204
|
+
"{prompt}"
|
|
2205
|
+
],
|
|
2206
|
+
modelArgs: ["--model", "{model}"],
|
|
2207
|
+
level: "official",
|
|
2208
|
+
note: "Add --mode json for structured event output. No read-only mode: the only built-in tool is a Python REPL and the worker and kernel processes are not a security sandbox."
|
|
2209
|
+
};
|
|
2210
|
+
modelListing = {
|
|
2211
|
+
args: ["model", "list"],
|
|
2212
|
+
searchArgs: [
|
|
2213
|
+
"model",
|
|
2214
|
+
"list",
|
|
2215
|
+
"{search}"
|
|
2216
|
+
],
|
|
2217
|
+
level: "official",
|
|
2218
|
+
note: "Lists models with configured provider authentication; accepts an optional fuzzy search. --list-models was removed."
|
|
2219
|
+
};
|
|
2220
|
+
mcpConfigs = [{
|
|
2221
|
+
path: "~/.prime/agent/settings.json",
|
|
2222
|
+
scope: "user",
|
|
2223
|
+
level: "official",
|
|
2224
|
+
format: "json",
|
|
2225
|
+
key: ["mcpServers"],
|
|
2226
|
+
dialect: "prime",
|
|
2227
|
+
note: "prime-agent mcp add/remove manage this map; project settings entries are ignored for execution. Stdio env values are {\"env\": \"NAME\"} references, written from \"${NAME}\" values; literals are refused because the kernel rejects them at connect time."
|
|
2228
|
+
}];
|
|
2229
|
+
agentsFile = "~/.prime/agent/AGENTS.md";
|
|
2230
|
+
detection = {
|
|
2231
|
+
envVars: ["PRIME_AGENT_KERNEL_OWNER_PID"],
|
|
2232
|
+
projectMarkers: [".prime/agent"]
|
|
2233
|
+
};
|
|
2234
|
+
parseModelListingOutput(stdout) {
|
|
2235
|
+
return parsePiModelTable(stdout, "Prime Agent");
|
|
1925
2236
|
}
|
|
1926
2237
|
};
|
|
1927
2238
|
const harnesses = [
|
|
@@ -1936,6 +2247,7 @@ const harnesses = [
|
|
|
1936
2247
|
MastraCode,
|
|
1937
2248
|
Omp,
|
|
1938
2249
|
OpenCode,
|
|
2250
|
+
PrimeAgent,
|
|
1939
2251
|
Pi
|
|
1940
2252
|
];
|
|
1941
2253
|
let registry;
|
|
@@ -2079,9 +2391,30 @@ function normalizeStandard(name, raw) {
|
|
|
2079
2391
|
enabled: booleanField(raw, "enabled")
|
|
2080
2392
|
});
|
|
2081
2393
|
}
|
|
2394
|
+
const ENV_REFERENCE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
2395
|
+
var McpEnvError = class extends Error {
|
|
2396
|
+
name = "McpEnvError";
|
|
2397
|
+
};
|
|
2398
|
+
function envReference(value) {
|
|
2399
|
+
return ENV_REFERENCE.exec(value)?.[1];
|
|
2400
|
+
}
|
|
2401
|
+
function primeEnv(value) {
|
|
2402
|
+
if (!isObjectRecord(value) || Array.isArray(value)) return void 0;
|
|
2403
|
+
const out = {};
|
|
2404
|
+
for (const [key, raw] of Object.entries(value)) if (typeof raw === "string") out[key] = raw;
|
|
2405
|
+
else if (isObjectRecord(raw) && typeof raw.env === "string") out[key] = `\${${raw.env}}`;
|
|
2406
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2407
|
+
}
|
|
2408
|
+
function normalizePrime(name, raw) {
|
|
2409
|
+
return compact({
|
|
2410
|
+
...normalizeStandard(name, raw),
|
|
2411
|
+
env: primeEnv(raw.env)
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2082
2414
|
function normalizeEntry(name, raw, dialect) {
|
|
2083
2415
|
if (dialect === "antigravity") return normalizeAntigravity(name, raw);
|
|
2084
2416
|
if (dialect === "opencode") return normalizeOpenCode(name, raw);
|
|
2417
|
+
if (dialect === "prime") return normalizePrime(name, raw);
|
|
2085
2418
|
return normalizeStandard(name, raw);
|
|
2086
2419
|
}
|
|
2087
2420
|
function denormalizeAntigravity(server) {
|
|
@@ -2117,9 +2450,44 @@ function denormalizeStandard(server) {
|
|
|
2117
2450
|
enabled: server.enabled
|
|
2118
2451
|
});
|
|
2119
2452
|
}
|
|
2453
|
+
function denormalizePrime(server) {
|
|
2454
|
+
const env = server.env ? Object.fromEntries(Object.entries(server.env).map(([key, value]) => [key, { env: requirePrimeReference(server, key, value) }])) : void 0;
|
|
2455
|
+
return compact({
|
|
2456
|
+
...denormalizeStandard(server),
|
|
2457
|
+
env
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
function requirePrimeReference(server, key, value) {
|
|
2461
|
+
const reference = envReference(value);
|
|
2462
|
+
if (reference === void 0) throw new McpEnvError(`${server.name}: Prime Agent takes only environment references in env; write ${key} as "\${NAME}" and export NAME where prime-agent runs`);
|
|
2463
|
+
return reference;
|
|
2464
|
+
}
|
|
2465
|
+
function resolveEnvReferences(server, dialect) {
|
|
2466
|
+
if (!server.env) return server;
|
|
2467
|
+
if (dialect === "prime") {
|
|
2468
|
+
for (const [key, value] of Object.entries(server.env)) requirePrimeReference(server, key, value);
|
|
2469
|
+
return server;
|
|
2470
|
+
}
|
|
2471
|
+
const env = {};
|
|
2472
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
2473
|
+
const reference = envReference(value);
|
|
2474
|
+
if (reference === void 0) {
|
|
2475
|
+
env[key] = value;
|
|
2476
|
+
continue;
|
|
2477
|
+
}
|
|
2478
|
+
const resolved = Object.hasOwn(process.env, reference) ? process.env[reference] : void 0;
|
|
2479
|
+
if (typeof resolved !== "string") throw new McpEnvError(`${server.name}: env ${key} references ${reference}, which is not set in this environment`);
|
|
2480
|
+
env[key] = resolved;
|
|
2481
|
+
}
|
|
2482
|
+
return {
|
|
2483
|
+
...server,
|
|
2484
|
+
env
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2120
2487
|
function denormalizeEntry(server, dialect) {
|
|
2121
2488
|
if (dialect === "antigravity") return denormalizeAntigravity(server);
|
|
2122
2489
|
if (dialect === "opencode") return denormalizeOpenCode(server);
|
|
2490
|
+
if (dialect === "prime") return denormalizePrime(server);
|
|
2123
2491
|
return denormalizeStandard(server);
|
|
2124
2492
|
}
|
|
2125
2493
|
function listingBase(entry, path) {
|
|
@@ -2256,15 +2624,16 @@ function removeTomlServer(path, key, name) {
|
|
|
2256
2624
|
}
|
|
2257
2625
|
function addMcpServer(harness, server, scope = "user", options = {}) {
|
|
2258
2626
|
const { entry, path } = writableConfig(harness, scope, options);
|
|
2627
|
+
const resolved = resolveEnvReferences(server, entry.dialect);
|
|
2259
2628
|
if (entry.format === "toml") return {
|
|
2260
2629
|
path,
|
|
2261
|
-
replaced: addTomlServer(path, entry.key,
|
|
2630
|
+
replaced: addTomlServer(path, entry.key, resolved)
|
|
2262
2631
|
};
|
|
2263
2632
|
const root = JSON.parse(readIfExists$1(path) ?? "{}");
|
|
2264
2633
|
const map = drill(root, entry.key, "create");
|
|
2265
2634
|
if (!map) throw new Error(`Config at ${path} has a non-object at ${entry.key.join(".")}`);
|
|
2266
2635
|
const replaced = Object.hasOwn(map, server.name);
|
|
2267
|
-
map[server.name] = denormalizeEntry(
|
|
2636
|
+
map[server.name] = denormalizeEntry(resolved, entry.dialect);
|
|
2268
2637
|
writeAtomically(path, `${JSON.stringify(root, null, 2)}\n`);
|
|
2269
2638
|
return {
|
|
2270
2639
|
path,
|
|
@@ -2406,7 +2775,7 @@ function canonical(server) {
|
|
|
2406
2775
|
return JSON.stringify(Object.fromEntries(Object.entries(server).sort(([a], [b]) => a.localeCompare(b))));
|
|
2407
2776
|
}
|
|
2408
2777
|
function expandHome(value, home) {
|
|
2409
|
-
return value.
|
|
2778
|
+
return value.replaceAll(/^~(?=\/|$)|\$\{HOME\}/g, () => home);
|
|
2410
2779
|
}
|
|
2411
2780
|
function expandServerHome(server, home) {
|
|
2412
2781
|
return compact({
|
|
@@ -2473,20 +2842,29 @@ function withdrawMasterServersFromExcludedHarness(harness, existing, masterNames
|
|
|
2473
2842
|
function syncIncludedHarness(harness, master, existing, masterNames, initialPath, options) {
|
|
2474
2843
|
const results = [];
|
|
2475
2844
|
let path = initialPath;
|
|
2476
|
-
|
|
2477
|
-
|
|
2845
|
+
const { dialect } = writableConfig(harness, "user", options).entry;
|
|
2846
|
+
for (const server of master.servers) try {
|
|
2847
|
+
const resolved = resolveEnvReferences(server, dialect);
|
|
2848
|
+
if (existing.get(server.name) === canonical(resolved)) {
|
|
2478
2849
|
results.push({
|
|
2479
2850
|
name: server.name,
|
|
2480
2851
|
action: "unchanged"
|
|
2481
2852
|
});
|
|
2482
2853
|
continue;
|
|
2483
2854
|
}
|
|
2484
|
-
const written = addMcpServer(harness,
|
|
2855
|
+
const written = addMcpServer(harness, resolved, "user", options);
|
|
2485
2856
|
path = written.path;
|
|
2486
2857
|
results.push({
|
|
2487
2858
|
name: server.name,
|
|
2488
2859
|
action: written.replaced ? "replaced" : "added"
|
|
2489
2860
|
});
|
|
2861
|
+
} catch (error) {
|
|
2862
|
+
if (!(error instanceof McpEnvError)) throw error;
|
|
2863
|
+
results.push({
|
|
2864
|
+
name: server.name,
|
|
2865
|
+
action: "skipped",
|
|
2866
|
+
reason: error.message
|
|
2867
|
+
});
|
|
2490
2868
|
}
|
|
2491
2869
|
for (const name of existing.keys()) {
|
|
2492
2870
|
if (masterNames.has(name)) continue;
|
|
@@ -2544,8 +2922,9 @@ function agentsConfigRecord(raw, configPath) {
|
|
|
2544
2922
|
if (typeof parsed !== "object" || parsed === null) throw new Error(`Agents config at ${configPath} is not an object`);
|
|
2545
2923
|
return parsed;
|
|
2546
2924
|
}
|
|
2547
|
-
function agentsSource(value, defaultSource, options) {
|
|
2548
|
-
if (
|
|
2925
|
+
function agentsSource(value, defaultSource, options, configPath) {
|
|
2926
|
+
if (value === void 0) return defaultSource;
|
|
2927
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`Agents config at ${configPath} has an invalid source: expected a path string`);
|
|
2549
2928
|
const expanded = resolvePathTemplate(value, options);
|
|
2550
2929
|
return isAbsolute(expanded) ? expanded : join(agntnConfigDir(options), expanded);
|
|
2551
2930
|
}
|
|
@@ -2555,9 +2934,9 @@ function agentsExcludes(value, configPath) {
|
|
|
2555
2934
|
return value;
|
|
2556
2935
|
}
|
|
2557
2936
|
function companionPath(value, configPath) {
|
|
2558
|
-
const normalized = normalize(value);
|
|
2559
|
-
if (normalized === "." || normalized === ".." || normalized.startsWith(
|
|
2560
|
-
return normalized.endsWith(
|
|
2937
|
+
const normalized = posix.normalize(value.replaceAll("\\", "/"));
|
|
2938
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith("../") || posix.isAbsolute(normalized) || win32.parse(value).root !== "") throw new Error(`Agents config at ${configPath} has an unsafe companion path: ${JSON.stringify(normalized)}`);
|
|
2939
|
+
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
2561
2940
|
}
|
|
2562
2941
|
function comparablePath(path) {
|
|
2563
2942
|
return resolve(path).toLowerCase();
|
|
@@ -2581,7 +2960,7 @@ function readAgentsConfig(options = {}) {
|
|
|
2581
2960
|
if (raw === null) return defaults;
|
|
2582
2961
|
const record = agentsConfigRecord(raw, configPath);
|
|
2583
2962
|
return {
|
|
2584
|
-
source: agentsSource(record.source, defaults.source, options),
|
|
2963
|
+
source: agentsSource(record.source, defaults.source, options, configPath),
|
|
2585
2964
|
companions: agentsCompanions(record.companions, configPath),
|
|
2586
2965
|
excludes: agentsExcludes(record.excludes, configPath),
|
|
2587
2966
|
configPath
|
|
@@ -2714,6 +3093,6 @@ function syncAgentsFiles(harnesses, check = false, options = {}) {
|
|
|
2714
3093
|
targets: harnesses.map((harness) => syncTarget(harness, config, check, options))
|
|
2715
3094
|
};
|
|
2716
3095
|
}
|
|
2717
|
-
export { Harness, addMcpServer, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers };
|
|
3096
|
+
export { Harness, McpEnvError, addMcpServer, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers };
|
|
2718
3097
|
|
|
2719
3098
|
//# sourceMappingURL=agents-sync.mjs.map
|