@tangle-network/agent-profile-materialize 0.11.0 → 0.13.0

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.
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Fork FACTS for prime-agent (PrimeIntellect-ai/prime-agent) — the constants,
3
+ * dialects and refusal reasons every executor that drives the fork needs, with
4
+ * no `node:fs` and no executor assumptions. The agent-dir MATERIALIZATION that
5
+ * consumes them lives in `./index.ts`; both consumers (cli-bridge's host-spawn
6
+ * backend on `--mode rpc`, sdk-provider-prime's in-sandbox adapter on
7
+ * `--mode acp`) read these instead of each keeping a private copy, because a
8
+ * fork bump must move one file, not two repositories.
9
+ *
10
+ * Every value here is verified against the pinned clone named by
11
+ * {@link PRIME_FORK_PIN}; `tests/prime-fork-pin.test.ts` re-asserts them
12
+ * against that clone when it is present, so a fork bump fails loudly in one
13
+ * place.
14
+ */
15
+ /** The prime-agent clone every constant in this module was read from. */
16
+ export declare const PRIME_FORK_PIN: {
17
+ readonly repo: "PrimeIntellect-ai/prime-agent";
18
+ readonly version: "0.7.0";
19
+ readonly commit: "be9e2fa0";
20
+ /** Local clone used by the fork-pin test when it exists. */
21
+ readonly clonePath: "/home/drew/bench-cache/prime-agent";
22
+ /** Package root inside the clone that the file:line citations are relative to. */
23
+ readonly packageRoot: "packages/coding-agent/src";
24
+ };
25
+ /**
26
+ * The fork's package identity, and why it cannot be installed from the npm
27
+ * registry.
28
+ *
29
+ * `@earendil-works/pi-coding-agent` on registry.npmjs.org serves the UPSTREAM
30
+ * pi line (0.84.x); the fork's 0.7.x versions were never published there. The
31
+ * two are wire-incompatible — different daemon schema revision, different
32
+ * config dir, different agent-dir env var (see
33
+ * {@link PRIME_AGENT_DIR_ENV_VARS}) — so `npm install -g` under that name
34
+ * yields an agent none of this lowering was verified against. The fork must be
35
+ * built from source and exposed under a `prime-agent` command name; its own
36
+ * `bin` entry is `pi` (fork package.json `bin`), which is exactly how the wrong
37
+ * binary ends up on `PATH` under the right-looking name.
38
+ */
39
+ export declare const PRIME_FORK_INSTALL: {
40
+ readonly npmName: "@earendil-works/pi-coding-agent";
41
+ /** The fork's own bin name — deliberately NOT the command to install it as. */
42
+ readonly forkBinName: "pi";
43
+ /** Command name an executor's binary path is expected to resolve to. */
44
+ readonly expectedCommandName: "prime-agent";
45
+ readonly sourceRepo: "https://github.com/PrimeIntellect-ai/prime-agent";
46
+ /** Built entrypoint to expose as `prime-agent`. */
47
+ readonly sourceEntrypoint: "packages/coding-agent/dist/bundle/cli.js";
48
+ readonly reason: string;
49
+ };
50
+ /**
51
+ * Env vars the agent resolves its config dir from, most-specific first.
52
+ *
53
+ * The fork derives the name from its own package identity —
54
+ * `ENV_AGENT_DIR = \`${envPrefix}_CODING_AGENT_DIR\`` where `envPrefix` comes
55
+ * from `package.json` `piConfig.name` (config.ts:485-498) — so the fork reads
56
+ * `PRIME_AGENT_CODING_AGENT_DIR` and upstream pi reads `PI_CODING_AGENT_DIR`.
57
+ * An executor that sets only the fork's name has its isolation SILENTLY
58
+ * disabled when the binary on PATH turns out to be upstream pi: `getAgentDir()`
59
+ * falls back to `homedir()/<CONFIG_DIR_NAME>` (config.ts:520-527), one shared
60
+ * dir — and therefore one shared self-modifying `harness_state.json` — for
61
+ * every session on the box. Setting BOTH names costs nothing and keeps the
62
+ * isolation fail-closed under either binary.
63
+ */
64
+ export declare const PRIME_AGENT_DIR_ENV_VARS: readonly ["PRIME_AGENT_CODING_AGENT_DIR", "PI_CODING_AGENT_DIR"];
65
+ /** Canonical agent-dir env var for the fork itself (config.ts:498). */
66
+ export declare const PRIME_AGENT_DIR_ENV_VAR: "PRIME_AGENT_CODING_AGENT_DIR";
67
+ /** Pin `agentDir` under every name the agent could resolve its config dir from. */
68
+ export declare function primeAgentDirEnv(agentDir: string): Record<string, string>;
69
+ /**
70
+ * Operator knobs an executor forwards into the agent when they are set.
71
+ *
72
+ * `PRIME_AGENT_KERNEL_VENV` is load-bearing for any executor that also pins
73
+ * HOME: the kernel venv defaults under `homedir()/.prime/agent/kernel-venv`
74
+ * (kernel/bootstrap.ts:331-342), so a fresh HOME re-bootstraps it through uv on
75
+ * every run unless the operator's venv path is forwarded
76
+ * (bootstrap.ts:340 override, :358 branch).
77
+ */
78
+ export declare const PRIME_KERNEL_ENV_PASSTHROUGH: readonly ["PRIME_AGENT_INSTALL_UV", "PRIME_AGENT_KERNEL_PYTHON", "PRIME_AGENT_KERNEL_VENV"];
79
+ /**
80
+ * Env vars an executor must NEVER forward from its own environment.
81
+ *
82
+ * These re-point the self-modifying continual-harness store OUTSIDE the agent
83
+ * dir (agent-session.ts:8805 hands the kernel its store location), which
84
+ * defeats per-session isolation without changing anything an executor inspects.
85
+ * Isolation is the fail-closed default; sharing state is an explicit opt-in
86
+ * that names a directory, never an inherited env var.
87
+ */
88
+ export declare const PRIME_HARNESS_STATE_ENV_DENYLIST: readonly ["RLM_GLOBAL_HARNESS_STATE_DIR", "RLM_HARNESS_STATE_DIR"];
89
+ /**
90
+ * A prime-agent IPython kernel that dies is never re-provisioned headlessly —
91
+ * every later `ipython` call fails with one of these markers until the process
92
+ * is replaced. An executor that cannot recognize them either reports a generic
93
+ * turn failure or burns its whole timeout against a permanently dead tool.
94
+ *
95
+ * Verified in `core/kernel/index.ts`: :723 ("forked kernel exited
96
+ * unexpectedly"), :750 ("Kernel exited before resolving ports"), :779 ("Kernel
97
+ * exited during startup"), :820/:837/:1172/:1295 ("Kernel has been shut down").
98
+ */
99
+ export declare const PRIME_KERNEL_DEAD_MARKERS: readonly string[];
100
+ /** The first kernel-death marker present in `text`, or undefined. */
101
+ export declare function matchPrimeKernelDeadMarker(text: string): string | undefined;
102
+ /**
103
+ * `--thinking` rungs the fork's argument parser accepts
104
+ * (cli/args.ts:62 `VALID_THINKING_LEVELS`, rejected at :175). Read from the
105
+ * fork itself rather than borrowed from upstream pi's measured table: the two
106
+ * agreeing today is a coincidence this module must not depend on.
107
+ */
108
+ export declare const PRIME_THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
109
+ export type PrimeThinkingLevel = (typeof PRIME_THINKING_LEVELS)[number];
110
+ /**
111
+ * Map the portable reasoning ladder onto the fork's `--thinking` value.
112
+ *
113
+ * Throws on an effort with no rung instead of returning undefined: omitting the
114
+ * flag runs the agent at its default thinking level while the caller believes
115
+ * the requested effort is in force, which is a silently dropped control.
116
+ */
117
+ export declare function primeThinkingLevel(effort: string | null | undefined): PrimeThinkingLevel | undefined;
118
+ /**
119
+ * Why `profile.mcp` (and any hub connection that resolves to MCP) is refused.
120
+ *
121
+ * The fork's `mcpServers` settings surface IS read at startup
122
+ * (settings-manager.ts:98-120,219) but a user-declared server never becomes
123
+ * model-visible: the host McpManager registers OAuth providers and serves
124
+ * mcp.config/refresh host-requests only (core/mcp/mcp-manager.ts:62-124),
125
+ * skips stdio servers entirely (mcp-manager.ts:73), and nothing generates a
126
+ * skill or prompt section for one. Model-visible MCP is exactly the built-in
127
+ * catalog skills, which are hand-written Python `McpIntegration` subclasses
128
+ * (prime-agent-runtime/src/rlm/mcp_base.py:112-125; only linear and notion
129
+ * ship). Materializing a per-run entry would run the agent WITHOUT the
130
+ * requested tools while the config on disk says otherwise.
131
+ */
132
+ export declare const PRIME_MCP_UNSUPPORTED_REASON: string;
133
+ /** Which half of a profile refusal this is: a fork limit, or a bad declaration. */
134
+ export type PrimeProfileErrorKind = "unsupported" | "invalid";
135
+ /**
136
+ * Refusal raised by the shared prime lowering, carrying the control and the
137
+ * reason separately so each executor can render them in its own error contract
138
+ * (an HTTP backend's typed `BackendError`, an SDK adapter's plain `Error`)
139
+ * without the reason text — which is where the fork evidence lives — being
140
+ * rewritten per repository.
141
+ */
142
+ export declare class PrimeProfileError extends Error {
143
+ readonly kind: PrimeProfileErrorKind;
144
+ readonly control: string;
145
+ readonly reason: string;
146
+ constructor(kind: PrimeProfileErrorKind, control: string, reason: string);
147
+ }
148
+ /** Raise the "the fork cannot honor this control" half of the refusal. */
149
+ export declare function rejectPrimeControl(control: string, reason: string): never;
150
+ /** Raise the "this declaration is malformed" half of the refusal. */
151
+ export declare function rejectPrimeDeclaration(control: string, reason: string): never;
152
+ /**
153
+ * The fork's continual-harness store exists but no longer parses.
154
+ *
155
+ * The store is the agent's own self-modification state — refinements, learned
156
+ * prompts, subagents it created mid-session — so an executor that reseeds it
157
+ * from empty destroys evidence and silently mutates the thing under study. The
158
+ * turn is failed instead; the file is the operator's to repair or remove.
159
+ */
160
+ export declare class PrimeHarnessStateError extends Error {
161
+ readonly statePath: string;
162
+ constructor(statePath: string, detail: string);
163
+ }
164
+ /**
165
+ * Strip `//` line comments and trailing commas, leaving string literals
166
+ * untouched — byte-for-byte the fork's own `stripJsonComments`
167
+ * (core/model-registry.ts), so a models.json the agent accepts is never refused
168
+ * by an executor and vice versa.
169
+ */
170
+ export declare function stripPrimeJsonComments(input: string): string;
171
+ /**
172
+ * Parse a models.json with the fork's own laxness and assert the one structural
173
+ * invariant it requires (a `providers` object). Throws naming `sourcePath`.
174
+ *
175
+ * This is the SHAPE and DIALECT only. Producing the file is deliberately not
176
+ * shared: an executor holding real operator credentials copies an
177
+ * operator-authored file and forwards only the env var names its `apiKey`
178
+ * entries reference, while an in-sandbox executor synthesizes a one-provider
179
+ * entry with a literal key that is a proxy dummy. Same schema, opposite threat
180
+ * models.
181
+ */
182
+ export declare function parsePrimeModelsJson(text: string, sourcePath: string): Record<string, unknown>;
183
+ /**
184
+ * Environment variables a models.json names as `apiKey` sources.
185
+ *
186
+ * prime resolves an `apiKey` value env-var first and LITERAL second
187
+ * (core/resolve-config-value.ts), so an executor that forwards exactly the
188
+ * named variables lets an operator keep secrets out of the file — and an
189
+ * executor that forwards NONE of them silently turns the variable name itself
190
+ * into the credential string.
191
+ */
192
+ export declare function primeApiKeyEnvNames(modelsConfig: Record<string, unknown>): string[];
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Fork FACTS for prime-agent (PrimeIntellect-ai/prime-agent) — the constants,
3
+ * dialects and refusal reasons every executor that drives the fork needs, with
4
+ * no `node:fs` and no executor assumptions. The agent-dir MATERIALIZATION that
5
+ * consumes them lives in `./index.ts`; both consumers (cli-bridge's host-spawn
6
+ * backend on `--mode rpc`, sdk-provider-prime's in-sandbox adapter on
7
+ * `--mode acp`) read these instead of each keeping a private copy, because a
8
+ * fork bump must move one file, not two repositories.
9
+ *
10
+ * Every value here is verified against the pinned clone named by
11
+ * {@link PRIME_FORK_PIN}; `tests/prime-fork-pin.test.ts` re-asserts them
12
+ * against that clone when it is present, so a fork bump fails loudly in one
13
+ * place.
14
+ */
15
+ /** The prime-agent clone every constant in this module was read from. */
16
+ export const PRIME_FORK_PIN = {
17
+ repo: "PrimeIntellect-ai/prime-agent",
18
+ version: "0.7.0",
19
+ commit: "be9e2fa0",
20
+ /** Local clone used by the fork-pin test when it exists. */
21
+ clonePath: "/home/drew/bench-cache/prime-agent",
22
+ /** Package root inside the clone that the file:line citations are relative to. */
23
+ packageRoot: "packages/coding-agent/src",
24
+ };
25
+ /**
26
+ * The fork's package identity, and why it cannot be installed from the npm
27
+ * registry.
28
+ *
29
+ * `@earendil-works/pi-coding-agent` on registry.npmjs.org serves the UPSTREAM
30
+ * pi line (0.84.x); the fork's 0.7.x versions were never published there. The
31
+ * two are wire-incompatible — different daemon schema revision, different
32
+ * config dir, different agent-dir env var (see
33
+ * {@link PRIME_AGENT_DIR_ENV_VARS}) — so `npm install -g` under that name
34
+ * yields an agent none of this lowering was verified against. The fork must be
35
+ * built from source and exposed under a `prime-agent` command name; its own
36
+ * `bin` entry is `pi` (fork package.json `bin`), which is exactly how the wrong
37
+ * binary ends up on `PATH` under the right-looking name.
38
+ */
39
+ export const PRIME_FORK_INSTALL = {
40
+ npmName: "@earendil-works/pi-coding-agent",
41
+ /** The fork's own bin name — deliberately NOT the command to install it as. */
42
+ forkBinName: "pi",
43
+ /** Command name an executor's binary path is expected to resolve to. */
44
+ expectedCommandName: "prime-agent",
45
+ sourceRepo: "https://github.com/PrimeIntellect-ai/prime-agent",
46
+ /** Built entrypoint to expose as `prime-agent`. */
47
+ sourceEntrypoint: "packages/coding-agent/dist/bundle/cli.js",
48
+ reason: "registry.npmjs.org serves the upstream pi line under @earendil-works/pi-coding-agent " +
49
+ "(0.84.x); the prime-agent fork's 0.7.x versions do not exist there. Installing from " +
50
+ "the registry yields a wire-incompatible agent with a different daemon schema, a " +
51
+ "different config dir, and a different agent-dir env var. Build from source and expose " +
52
+ "packages/coding-agent/dist/bundle/cli.js as `prime-agent`.",
53
+ };
54
+ /**
55
+ * Env vars the agent resolves its config dir from, most-specific first.
56
+ *
57
+ * The fork derives the name from its own package identity —
58
+ * `ENV_AGENT_DIR = \`${envPrefix}_CODING_AGENT_DIR\`` where `envPrefix` comes
59
+ * from `package.json` `piConfig.name` (config.ts:485-498) — so the fork reads
60
+ * `PRIME_AGENT_CODING_AGENT_DIR` and upstream pi reads `PI_CODING_AGENT_DIR`.
61
+ * An executor that sets only the fork's name has its isolation SILENTLY
62
+ * disabled when the binary on PATH turns out to be upstream pi: `getAgentDir()`
63
+ * falls back to `homedir()/<CONFIG_DIR_NAME>` (config.ts:520-527), one shared
64
+ * dir — and therefore one shared self-modifying `harness_state.json` — for
65
+ * every session on the box. Setting BOTH names costs nothing and keeps the
66
+ * isolation fail-closed under either binary.
67
+ */
68
+ export const PRIME_AGENT_DIR_ENV_VARS = [
69
+ "PRIME_AGENT_CODING_AGENT_DIR",
70
+ "PI_CODING_AGENT_DIR",
71
+ ];
72
+ /** Canonical agent-dir env var for the fork itself (config.ts:498). */
73
+ export const PRIME_AGENT_DIR_ENV_VAR = PRIME_AGENT_DIR_ENV_VARS[0];
74
+ /** Pin `agentDir` under every name the agent could resolve its config dir from. */
75
+ export function primeAgentDirEnv(agentDir) {
76
+ const env = {};
77
+ for (const name of PRIME_AGENT_DIR_ENV_VARS)
78
+ env[name] = agentDir;
79
+ return env;
80
+ }
81
+ /**
82
+ * Operator knobs an executor forwards into the agent when they are set.
83
+ *
84
+ * `PRIME_AGENT_KERNEL_VENV` is load-bearing for any executor that also pins
85
+ * HOME: the kernel venv defaults under `homedir()/.prime/agent/kernel-venv`
86
+ * (kernel/bootstrap.ts:331-342), so a fresh HOME re-bootstraps it through uv on
87
+ * every run unless the operator's venv path is forwarded
88
+ * (bootstrap.ts:340 override, :358 branch).
89
+ */
90
+ export const PRIME_KERNEL_ENV_PASSTHROUGH = [
91
+ /** "1" lets a headless first run install uv itself; "0" forbids it. */
92
+ "PRIME_AGENT_INSTALL_UV",
93
+ /** Offline escape hatch: pin the kernel venv python instead of uv-provisioning. */
94
+ "PRIME_AGENT_KERNEL_PYTHON",
95
+ /** Reuse an operator-provisioned kernel venv instead of re-bootstrapping it. */
96
+ "PRIME_AGENT_KERNEL_VENV",
97
+ ];
98
+ /**
99
+ * Env vars an executor must NEVER forward from its own environment.
100
+ *
101
+ * These re-point the self-modifying continual-harness store OUTSIDE the agent
102
+ * dir (agent-session.ts:8805 hands the kernel its store location), which
103
+ * defeats per-session isolation without changing anything an executor inspects.
104
+ * Isolation is the fail-closed default; sharing state is an explicit opt-in
105
+ * that names a directory, never an inherited env var.
106
+ */
107
+ export const PRIME_HARNESS_STATE_ENV_DENYLIST = [
108
+ "RLM_GLOBAL_HARNESS_STATE_DIR",
109
+ "RLM_HARNESS_STATE_DIR",
110
+ ];
111
+ /**
112
+ * A prime-agent IPython kernel that dies is never re-provisioned headlessly —
113
+ * every later `ipython` call fails with one of these markers until the process
114
+ * is replaced. An executor that cannot recognize them either reports a generic
115
+ * turn failure or burns its whole timeout against a permanently dead tool.
116
+ *
117
+ * Verified in `core/kernel/index.ts`: :723 ("forked kernel exited
118
+ * unexpectedly"), :750 ("Kernel exited before resolving ports"), :779 ("Kernel
119
+ * exited during startup"), :820/:837/:1172/:1295 ("Kernel has been shut down").
120
+ */
121
+ export const PRIME_KERNEL_DEAD_MARKERS = [
122
+ "Kernel has been shut down",
123
+ "Kernel exited before resolving ports",
124
+ "Kernel exited during startup",
125
+ "forked kernel exited unexpectedly",
126
+ ];
127
+ /** The first kernel-death marker present in `text`, or undefined. */
128
+ export function matchPrimeKernelDeadMarker(text) {
129
+ return PRIME_KERNEL_DEAD_MARKERS.find((marker) => text.includes(marker));
130
+ }
131
+ /**
132
+ * `--thinking` rungs the fork's argument parser accepts
133
+ * (cli/args.ts:62 `VALID_THINKING_LEVELS`, rejected at :175). Read from the
134
+ * fork itself rather than borrowed from upstream pi's measured table: the two
135
+ * agreeing today is a coincidence this module must not depend on.
136
+ */
137
+ export const PRIME_THINKING_LEVELS = [
138
+ "off",
139
+ "minimal",
140
+ "low",
141
+ "medium",
142
+ "high",
143
+ "xhigh",
144
+ "max",
145
+ ];
146
+ /** Canonical portable effort → fork `--thinking` rung. */
147
+ const PRIME_THINKING_BY_EFFORT = {
148
+ none: "off",
149
+ minimal: "minimal",
150
+ low: "low",
151
+ medium: "medium",
152
+ high: "high",
153
+ xhigh: "xhigh",
154
+ // The fork carries `max` above `xhigh`, so the ceiling level reaches it.
155
+ ultracode: "max",
156
+ };
157
+ /**
158
+ * Map the portable reasoning ladder onto the fork's `--thinking` value.
159
+ *
160
+ * Throws on an effort with no rung instead of returning undefined: omitting the
161
+ * flag runs the agent at its default thinking level while the caller believes
162
+ * the requested effort is in force, which is a silently dropped control.
163
+ */
164
+ export function primeThinkingLevel(effort) {
165
+ if (effort === undefined || effort === null || effort === "")
166
+ return undefined;
167
+ const level = PRIME_THINKING_BY_EFFORT[effort];
168
+ if (level === undefined) {
169
+ throw new PrimeProfileError("invalid", "model.reasoningEffort", `prime-agent has no --thinking rung for ${JSON.stringify(effort)} ` +
170
+ `(fork cli/args.ts:62 accepts ${PRIME_THINKING_LEVELS.join(", ")}).`);
171
+ }
172
+ return level;
173
+ }
174
+ /**
175
+ * Why `profile.mcp` (and any hub connection that resolves to MCP) is refused.
176
+ *
177
+ * The fork's `mcpServers` settings surface IS read at startup
178
+ * (settings-manager.ts:98-120,219) but a user-declared server never becomes
179
+ * model-visible: the host McpManager registers OAuth providers and serves
180
+ * mcp.config/refresh host-requests only (core/mcp/mcp-manager.ts:62-124),
181
+ * skips stdio servers entirely (mcp-manager.ts:73), and nothing generates a
182
+ * skill or prompt section for one. Model-visible MCP is exactly the built-in
183
+ * catalog skills, which are hand-written Python `McpIntegration` subclasses
184
+ * (prime-agent-runtime/src/rlm/mcp_base.py:112-125; only linear and notion
185
+ * ship). Materializing a per-run entry would run the agent WITHOUT the
186
+ * requested tools while the config on disk says otherwise.
187
+ */
188
+ export const PRIME_MCP_UNSUPPORTED_REASON = "a user-declared mcpServers entry is read from the fork's settings.json at startup but never " +
189
+ "becomes model-visible: the host only registers OAuth/config for it (fork core/mcp/" +
190
+ "mcp-manager.ts:62-124), skips stdio servers entirely (mcp-manager.ts:73), and generates no " +
191
+ "skill or prompt section for it. Model-visible MCP is exactly the built-in Python " +
192
+ "McpIntegration catalog skills (prime-agent-runtime/src/rlm/mcp_base.py:112-125), which no " +
193
+ "per-run configuration creates — so materializing it would be a silently dropped control.";
194
+ /**
195
+ * Refusal raised by the shared prime lowering, carrying the control and the
196
+ * reason separately so each executor can render them in its own error contract
197
+ * (an HTTP backend's typed `BackendError`, an SDK adapter's plain `Error`)
198
+ * without the reason text — which is where the fork evidence lives — being
199
+ * rewritten per repository.
200
+ */
201
+ export class PrimeProfileError extends Error {
202
+ kind;
203
+ control;
204
+ reason;
205
+ constructor(kind, control, reason) {
206
+ super(kind === "unsupported"
207
+ ? `prime does not support AgentProfile ${control}: ${reason}`
208
+ : `prime cannot accept AgentProfile ${control}: ${reason}`);
209
+ this.name = "PrimeProfileError";
210
+ this.kind = kind;
211
+ this.control = control;
212
+ this.reason = reason;
213
+ }
214
+ }
215
+ /** Raise the "the fork cannot honor this control" half of the refusal. */
216
+ export function rejectPrimeControl(control, reason) {
217
+ throw new PrimeProfileError("unsupported", control, reason);
218
+ }
219
+ /** Raise the "this declaration is malformed" half of the refusal. */
220
+ export function rejectPrimeDeclaration(control, reason) {
221
+ throw new PrimeProfileError("invalid", control, reason);
222
+ }
223
+ /**
224
+ * The fork's continual-harness store exists but no longer parses.
225
+ *
226
+ * The store is the agent's own self-modification state — refinements, learned
227
+ * prompts, subagents it created mid-session — so an executor that reseeds it
228
+ * from empty destroys evidence and silently mutates the thing under study. The
229
+ * turn is failed instead; the file is the operator's to repair or remove.
230
+ */
231
+ export class PrimeHarnessStateError extends Error {
232
+ statePath;
233
+ constructor(statePath, detail) {
234
+ super(`prime harness state ${statePath} is not valid JSON and may hold agent self-modification ` +
235
+ `state; refusing to overwrite it — repair or remove the file to continue (${detail})`);
236
+ this.name = "PrimeHarnessStateError";
237
+ this.statePath = statePath;
238
+ }
239
+ }
240
+ /**
241
+ * Strip `//` line comments and trailing commas, leaving string literals
242
+ * untouched — byte-for-byte the fork's own `stripJsonComments`
243
+ * (core/model-registry.ts), so a models.json the agent accepts is never refused
244
+ * by an executor and vice versa.
245
+ */
246
+ export function stripPrimeJsonComments(input) {
247
+ return input
248
+ .replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*/g, (m) => (m[0] === '"' ? m : ""))
249
+ .replace(/"(?:\\.|[^"\\])*"|,(\s*[}\]])/g, (m, tail) => tail !== undefined ? tail : m[0] === '"' ? m : "");
250
+ }
251
+ /**
252
+ * Parse a models.json with the fork's own laxness and assert the one structural
253
+ * invariant it requires (a `providers` object). Throws naming `sourcePath`.
254
+ *
255
+ * This is the SHAPE and DIALECT only. Producing the file is deliberately not
256
+ * shared: an executor holding real operator credentials copies an
257
+ * operator-authored file and forwards only the env var names its `apiKey`
258
+ * entries reference, while an in-sandbox executor synthesizes a one-provider
259
+ * entry with a literal key that is a proxy dummy. Same schema, opposite threat
260
+ * models.
261
+ */
262
+ export function parsePrimeModelsJson(text, sourcePath) {
263
+ let parsed;
264
+ try {
265
+ parsed = JSON.parse(stripPrimeJsonComments(text));
266
+ }
267
+ catch (err) {
268
+ throw new Error(`prime models.json ${sourcePath} is not valid JSON: ${err.message}`);
269
+ }
270
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
271
+ throw new Error(`prime models.json ${sourcePath} must be a JSON object`);
272
+ }
273
+ const providers = parsed.providers;
274
+ if (!providers || typeof providers !== "object" || Array.isArray(providers)) {
275
+ throw new Error(`prime models.json ${sourcePath} must carry a "providers" object (prime's models.json schema)`);
276
+ }
277
+ return parsed;
278
+ }
279
+ /**
280
+ * Environment variables a models.json names as `apiKey` sources.
281
+ *
282
+ * prime resolves an `apiKey` value env-var first and LITERAL second
283
+ * (core/resolve-config-value.ts), so an executor that forwards exactly the
284
+ * named variables lets an operator keep secrets out of the file — and an
285
+ * executor that forwards NONE of them silently turns the variable name itself
286
+ * into the credential string.
287
+ */
288
+ export function primeApiKeyEnvNames(modelsConfig) {
289
+ const names = [];
290
+ const providers = modelsConfig.providers;
291
+ if (!providers || typeof providers !== "object")
292
+ return names;
293
+ for (const provider of Object.values(providers)) {
294
+ if (!provider || typeof provider !== "object")
295
+ continue;
296
+ const key = provider.apiKey;
297
+ if (typeof key !== "string" || !/^[A-Z_][A-Z0-9_]*$/.test(key))
298
+ continue;
299
+ if (!names.includes(key))
300
+ names.push(key);
301
+ }
302
+ return names;
303
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-profile-materialize",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Materialize an AgentProfile into the native per-harness workspace files (the one shared materializer for cli-bridge, agent-dev-container, and VB).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",