@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.
@@ -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 { basename, dirname, isAbsolute, join, normalize, resolve, sep } from "node:path";
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.replace(/^~(?=\/|$)/, homeDir).replaceAll("${HOME}", homeDir).replaceAll("${PROJECT_ROOT}", projectRoot).replaceAll(/%([^%]+)%/g, (match, name) => process.env[name] ?? match);
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
- let stdout = "";
76
- let stderr = "";
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", (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: timedOut ? null : code,
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
- const alternateMode = ALTERNATE_INVOCATION_MODE[mode];
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: ["exec", "{prompt}"],
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 Pi token count: ${JSON.stringify(value)}`);
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 Pi ${column} value: ${JSON.stringify(value)}`);
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
- const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1907
- const first = lines[0];
1908
- if (!first) throw new Error("Unexpected empty Pi model-list output");
1909
- if (first.startsWith("No models available.") || first.startsWith("No models matching ")) return [];
1910
- const headers = first.split(/\s{2,}/);
1911
- if (headers.length !== MODEL_HEADERS.length || headers.some((value, i) => value !== MODEL_HEADERS[i])) throw new Error(`Unexpected Pi model-list header: ${JSON.stringify(first)}`);
1912
- return lines.slice(1).map((line) => {
1913
- const columns = line.split(/\s{2,}/);
1914
- const [provider, id, context, maxOutput, thinking, images] = columns;
1915
- if (columns.length !== MODEL_HEADERS.length || !provider || !id || !context || !maxOutput || !thinking || !images) throw new Error(`Unexpected Pi model-list row: ${JSON.stringify(line)}`);
1916
- return {
1917
- provider,
1918
- id,
1919
- contextWindow: parseTokenCount(context),
1920
- maxOutputTokens: parseTokenCount(maxOutput),
1921
- thinking: parseBoolean(thinking, "thinking"),
1922
- images: parseBoolean(images, "images")
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, server)
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(server, entry.dialect);
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.replace(/^~(?=\/|$)/, home).replaceAll("${HOME}", home);
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
- for (const server of master.servers) {
2477
- if (existing.get(server.name) === canonical(server)) {
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, server, "user", options);
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 (typeof value !== "string") return defaultSource;
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(`..${sep}`) || isAbsolute(normalized)) throw new Error(`Agents config at ${configPath} has an unsafe companion path: ${JSON.stringify(normalized)}`);
2560
- return normalized.endsWith(sep) ? normalized.slice(0, -sep.length) : normalized;
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