@tangle-network/agent-profile-materialize 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/index.d.ts +1226 -29
  2. package/dist/index.js +4987 -23
  3. package/package.json +7 -5
  4. package/dist/candidate-types.d.ts +0 -36
  5. package/dist/candidate-types.js +0 -1
  6. package/dist/candidate.d.ts +0 -9
  7. package/dist/candidate.js +0 -283
  8. package/dist/commands.d.ts +0 -2
  9. package/dist/commands.js +0 -32
  10. package/dist/control-maps.d.ts +0 -58
  11. package/dist/control-maps.js +0 -277
  12. package/dist/execution-controls.d.ts +0 -5
  13. package/dist/execution-controls.js +0 -199
  14. package/dist/harness-prompt.d.ts +0 -22
  15. package/dist/harness-prompt.js +0 -64
  16. package/dist/harness.d.ts +0 -131
  17. package/dist/harness.js +0 -416
  18. package/dist/hooks.d.ts +0 -6
  19. package/dist/hooks.js +0 -250
  20. package/dist/managed-workspace-bind.d.ts +0 -4
  21. package/dist/managed-workspace-bind.js +0 -106
  22. package/dist/managed-workspace-stage.d.ts +0 -25
  23. package/dist/managed-workspace-stage.js +0 -199
  24. package/dist/managed-workspace-types.d.ts +0 -38
  25. package/dist/managed-workspace-types.js +0 -1
  26. package/dist/managed-workspace.d.ts +0 -31
  27. package/dist/managed-workspace.js +0 -271
  28. package/dist/mcp-config-ownership.d.ts +0 -13
  29. package/dist/mcp-config-ownership.js +0 -79
  30. package/dist/mcp-opencode.d.ts +0 -25
  31. package/dist/mcp-opencode.js +0 -52
  32. package/dist/mcp-timeouts.d.ts +0 -7
  33. package/dist/mcp-timeouts.js +0 -48
  34. package/dist/mcp.d.ts +0 -4
  35. package/dist/mcp.js +0 -246
  36. package/dist/model-id.d.ts +0 -55
  37. package/dist/model-id.js +0 -67
  38. package/dist/pi-models-config.d.ts +0 -32
  39. package/dist/pi-models-config.js +0 -65
  40. package/dist/plan-application.d.ts +0 -10
  41. package/dist/plan-application.js +0 -190
  42. package/dist/plan-digest.d.ts +0 -18
  43. package/dist/plan-digest.js +0 -116
  44. package/dist/plan-json.d.ts +0 -13
  45. package/dist/plan-json.js +0 -91
  46. package/dist/plan-normalize.d.ts +0 -12
  47. package/dist/plan-normalize.js +0 -168
  48. package/dist/plan-prompt.d.ts +0 -38
  49. package/dist/plan-prompt.js +0 -49
  50. package/dist/plan-support.d.ts +0 -9
  51. package/dist/plan-support.js +0 -22
  52. package/dist/plan-types.d.ts +0 -341
  53. package/dist/plan-types.js +0 -105
  54. package/dist/prime-agent-dir.d.ts +0 -64
  55. package/dist/prime-agent-dir.js +0 -173
  56. package/dist/prime-fork.d.ts +0 -155
  57. package/dist/prime-fork.js +0 -262
  58. package/dist/prime-profile.d.ts +0 -88
  59. package/dist/prime-profile.js +0 -297
  60. package/dist/profile-bundle-cache.d.ts +0 -37
  61. package/dist/profile-bundle-cache.js +0 -320
  62. package/dist/profile-bundle-key.d.ts +0 -69
  63. package/dist/profile-bundle-key.js +0 -62
  64. package/dist/profile-bundle-lru.d.ts +0 -56
  65. package/dist/profile-bundle-lru.js +0 -220
  66. package/dist/profile-materializer.d.ts +0 -24
  67. package/dist/profile-materializer.js +0 -114
  68. package/dist/profile-plan.d.ts +0 -16
  69. package/dist/profile-plan.js +0 -90
  70. package/dist/profile-prompt.d.ts +0 -4
  71. package/dist/profile-prompt.js +0 -107
  72. package/dist/profile-support.d.ts +0 -8
  73. package/dist/profile-support.js +0 -6
  74. package/dist/resource-lowering.d.ts +0 -6
  75. package/dist/resource-lowering.js +0 -103
  76. package/dist/resource-resolution.d.ts +0 -27
  77. package/dist/resource-resolution.js +0 -265
  78. package/dist/secret-concurrent-revision.d.ts +0 -30
  79. package/dist/secret-concurrent-revision.js +0 -249
  80. package/dist/secret-session-view.d.ts +0 -25
  81. package/dist/secret-session-view.js +0 -166
  82. package/dist/subagents.d.ts +0 -6
  83. package/dist/subagents.js +0 -137
  84. package/dist/tool-bin-path.d.ts +0 -23
  85. package/dist/tool-bin-path.js +0 -61
  86. package/dist/workspace-apply-files.d.ts +0 -57
  87. package/dist/workspace-apply-files.js +0 -193
  88. package/dist/workspace-apply-publish.d.ts +0 -22
  89. package/dist/workspace-apply-publish.js +0 -178
  90. package/dist/workspace-apply.d.ts +0 -11
  91. package/dist/workspace-apply.js +0 -124
  92. package/dist/workspace-files.d.ts +0 -8
  93. package/dist/workspace-files.js +0 -46
  94. package/dist/workspace-launch.d.ts +0 -3
  95. package/dist/workspace-launch.js +0 -13
@@ -1,277 +0,0 @@
1
- export function normalizedControlName(name) {
2
- return name.replace(/[^a-zA-Z0-9_]/g, "").toLowerCase();
3
- }
4
- function nativeWebToolKind(name) {
5
- const posture = resolveNativeWebTools({ [name]: true });
6
- if (posture.search === true && posture.fetch === undefined)
7
- return "search";
8
- if (posture.fetch === true && posture.search === undefined)
9
- return "fetch";
10
- return undefined;
11
- }
12
- const CLAUDE_TOOL_NAMES = {
13
- agent: "Agent",
14
- askuserquestion: "AskUserQuestion",
15
- bash: "Bash",
16
- edit: "Edit",
17
- glob: "Glob",
18
- grep: "Grep",
19
- notebookedit: "NotebookEdit",
20
- read: "Read",
21
- skill: "Skill",
22
- task: "Task",
23
- taskoutput: "TaskOutput",
24
- todowrite: "TodoWrite",
25
- webfetch: "WebFetch",
26
- websearch: "WebSearch",
27
- write: "Write",
28
- };
29
- /** Tool IDs accepted by Claude Code. */
30
- export const CLAUDE_CODE_NATIVE_TOOL_IDS = Object.values(CLAUDE_TOOL_NAMES).sort();
31
- /** Permission IDs accepted by Claude Code (same as tool IDs). */
32
- export const CLAUDE_CODE_NATIVE_PERMISSION_IDS = CLAUDE_CODE_NATIVE_TOOL_IDS;
33
- export function claudeToolName(name) {
34
- const web = nativeWebToolKind(name);
35
- if (web === "search")
36
- return "WebSearch";
37
- if (web === "fetch")
38
- return "WebFetch";
39
- return CLAUDE_TOOL_NAMES[normalizedControlName(name)];
40
- }
41
- /** Tool IDs reported by the OpenCode binary shipped in the Sandbox image. */
42
- export const OPENCODE_NATIVE_TOOL_IDS = [
43
- "apply_patch",
44
- "bash",
45
- "codesearch",
46
- "edit",
47
- "glob",
48
- "grep",
49
- "question",
50
- "read",
51
- "skill",
52
- "task",
53
- "todowrite",
54
- "webfetch",
55
- "websearch",
56
- "write",
57
- ];
58
- const OPENCODE_NATIVE_TOOL_ID_SET = new Set(OPENCODE_NATIVE_TOOL_IDS);
59
- /** Permission IDs accepted by OpenCode (tools except read, plus special permissions). */
60
- export const OPENCODE_NATIVE_PERMISSION_IDS = [
61
- "apply_patch",
62
- "bash",
63
- "codesearch",
64
- "doom_loop",
65
- "edit",
66
- "external_directory",
67
- "glob",
68
- "grep",
69
- "mcp",
70
- "question",
71
- "skill",
72
- "task",
73
- "todowrite",
74
- "webfetch",
75
- "websearch",
76
- "write",
77
- ];
78
- /** Resolve portable and legacy profile names to current OpenCode tool IDs. */
79
- export function openCodeNativeToolId(name) {
80
- const web = nativeWebToolKind(name);
81
- if (web === "search")
82
- return "websearch";
83
- if (web === "fetch")
84
- return "webfetch";
85
- const normalized = normalizedControlName(name);
86
- if (normalized === "patch")
87
- return "apply_patch";
88
- return OPENCODE_NATIVE_TOOL_ID_SET.has(normalized) ? normalized : undefined;
89
- }
90
- export function openCodeToolName(name) {
91
- return openCodeNativeToolId(name);
92
- }
93
- export function openCodePermissionName(name) {
94
- if (name.includes("*") || name.includes("?"))
95
- return undefined;
96
- const normalized = normalizedControlName(name);
97
- if (["write", "edit"].includes(normalized))
98
- return "edit";
99
- const web = nativeWebToolKind(name);
100
- if (web === "search")
101
- return "websearch";
102
- if (web === "fetch")
103
- return "webfetch";
104
- if (["bash", "mcp", "doom_loop", "external_directory"].includes(normalized)) {
105
- return normalized;
106
- }
107
- return normalized !== "read" && OPENCODE_NATIVE_TOOL_ID_SET.has(normalized)
108
- ? normalized
109
- : undefined;
110
- }
111
- /** OpenCode 1.x registers MCP tools as sanitized server + "_" + tool.
112
- * Accept exact tools and a server-wide rule only for this profile's servers.
113
- * A generic permission spelling must never become a grant to an undeclared MCP.
114
- */
115
- export function openCodeDeclaredMcpPermissionName(name, aliases) {
116
- const servers = aliases.map((alias) => ({
117
- alias,
118
- native: alias.replace(/[^a-zA-Z0-9_-]/g, "_"),
119
- }));
120
- const matches = servers.filter(({ native }) => name.startsWith(`${native}_`));
121
- const match = matches[0];
122
- if (!match)
123
- return undefined;
124
- const { native } = match;
125
- const tool = name.slice(native.length + 1);
126
- if (tool !== "*" && !/^[a-zA-Z0-9_-]+$/.test(tool))
127
- return undefined;
128
- // Colliding or nested prefixes cannot identify one server's authority:
129
- // foo_* also matches foo_bar_read, including a server named foo_bar.
130
- if (matches.length !== 1 ||
131
- servers.some((server) => server !== matches[0] &&
132
- (server.native.startsWith(`${native}_`) ||
133
- native.startsWith(`${server.native}_`)))) {
134
- throw new Error(`opencode: ambiguous declared MCP permission "${name}"`);
135
- }
136
- if (tool !== "*" && openCodePermissionName(name) !== undefined) {
137
- throw new Error(`opencode: permission "${name}" names both a native and MCP tool`);
138
- }
139
- return name;
140
- }
141
- export const PI_EXECUTION_CONTROL_EXTENSION_PATH = ".tangle/pi-execution-controls.ts";
142
- /**
143
- * Translate only portable spellings whose Pi equivalent differs.
144
- *
145
- * Every other exact name is resolved against Pi's live registry, keeping
146
- * extension and MCP tools profile-configurable without a static catalog.
147
- */
148
- export function piToolId(name) {
149
- const trimmed = name.trim();
150
- if (trimmed.length === 0)
151
- return undefined;
152
- const portableName = trimmed.toLowerCase();
153
- if (portableName === "shell")
154
- return "bash";
155
- if (portableName === "glob")
156
- return "find";
157
- return trimmed;
158
- }
159
- /**
160
- * Map tool switches to native ids; spellings of one tool keep its explicit off.
161
- *
162
- * Products compose profiles in layers: a shared allowlist, then a product
163
- * constraint that disables a native tool under its own spelling. Physim adds
164
- * `webfetch: false` over an allowlist holding `web_fetch: true`. Off is the
165
- * only value that honours every layer, so it wins regardless of entry order.
166
- */
167
- export function mapSupportedBooleanEntries(entries, mapName, onUnsupported) {
168
- return mapControlEntries(entries, mapName, onUnsupported, (left, right) => left && right);
169
- }
170
- export function permissionAllowsEverything(policy) {
171
- return typeof policy === "string"
172
- ? policy === "allow"
173
- : Object.values(policy).every((value) => value === "allow");
174
- }
175
- export function mapSupportedPermissionEntries(entries, mapName, onUnsupported, label) {
176
- return mapControlEntries(entries, mapName, onUnsupported, (left, right, nativeName) => mergePermissionPolicies(left, right, `${label} "${nativeName}"`));
177
- }
178
- function mapControlEntries(entries, mapName, onUnsupported, merge) {
179
- const mapped = new Map();
180
- for (const [name, value] of entries) {
181
- const nativeName = mapName(name);
182
- if (!nativeName) {
183
- onUnsupported(name);
184
- continue;
185
- }
186
- const previous = mapped.get(nativeName);
187
- mapped.set(nativeName, previous === undefined ? value : merge(previous, value, nativeName));
188
- }
189
- return [...mapped.entries()].sort(([left], [right]) => compareStrings(left, right));
190
- }
191
- const PERMISSION_RESTRICTION = {
192
- allow: 0,
193
- ask: 1,
194
- deny: 2,
195
- };
196
- function stricterDecision(left, right) {
197
- return PERMISSION_RESTRICTION[right] > PERMISSION_RESTRICTION[left]
198
- ? right
199
- : left;
200
- }
201
- /**
202
- * Merge policies whose names resolve to one native tool; the stricter
203
- * decision wins, as an explicit off does for tool switches.
204
- *
205
- * Pattern rules merge only when both spellings list the same patterns in the
206
- * same order. OpenCode applies the last matching pattern, so any other pair
207
- * has no merge at least as strict as each spelling: `{"*": "deny"}` beside
208
- * `{"https://x/*": "allow"}` would allow x. Those pairs stay contradictions
209
- * that `executionControlConflict` reports at admission.
210
- */
211
- function mergePermissionPolicies(left, right, label) {
212
- if (typeof left === "string" && typeof right === "string") {
213
- return stricterDecision(left, right);
214
- }
215
- if (typeof left === "string" || typeof right === "string") {
216
- throw new Error(`${label} aliases mix a whole-tool decision with pattern rules`);
217
- }
218
- const patterns = Object.keys(left);
219
- const rightPatterns = Object.keys(right);
220
- if (patterns.length !== rightPatterns.length ||
221
- rightPatterns.some((pattern, index) => pattern !== patterns[index])) {
222
- throw new Error(`${label} aliases set different pattern rules`);
223
- }
224
- return Object.fromEntries(Object.entries(left).map(([pattern, decision]) => [
225
- pattern,
226
- stricterDecision(decision, right[pattern] ?? decision),
227
- ]));
228
- }
229
- export function claudePermissionRules(entries) {
230
- const rules = {
231
- allow: [],
232
- ask: [],
233
- deny: [],
234
- };
235
- for (const [tool, policy] of entries) {
236
- if (typeof policy === "string") {
237
- rules[policy].push(tool);
238
- continue;
239
- }
240
- for (const [specifier, decision] of Object.entries(policy).sort(([left], [right]) => compareStrings(left, right))) {
241
- rules[decision].push(specifier === "*" ? tool : `${tool}(${specifier})`);
242
- }
243
- }
244
- rules.allow.sort(compareStrings);
245
- rules.ask.sort(compareStrings);
246
- rules.deny.sort(compareStrings);
247
- return rules;
248
- }
249
- /**
250
- * Give each Claude rule one decision. A tool switch and a permission can name
251
- * the same rule; the stricter decision wins, as deny already beats allow
252
- * inside Claude Code.
253
- */
254
- export function sealClaudePermissionRules(rules) {
255
- const owner = new Map();
256
- for (const decision of ["allow", "ask", "deny"]) {
257
- for (const rule of rules[decision]) {
258
- const previous = owner.get(rule);
259
- owner.set(rule, previous === undefined
260
- ? decision
261
- : stricterDecision(previous, decision));
262
- }
263
- }
264
- const sealed = {
265
- allow: [],
266
- ask: [],
267
- deny: [],
268
- };
269
- for (const [rule, decision] of owner)
270
- sealed[decision].push(rule);
271
- sealed.allow.sort(compareStrings);
272
- sealed.ask.sort(compareStrings);
273
- sealed.deny.sort(compareStrings);
274
- return sealed;
275
- }
276
- import { resolveNativeWebTools } from "@tangle-network/agent-interface";
277
- import { compareStrings } from "./workspace-files.js";
@@ -1,5 +0,0 @@
1
- export declare function materializeExecutionControls(h: HarnessId, tools: Record<string, boolean> | undefined, permissions: Record<string, AgentProfilePermission> | undefined, modes: AgentProfile["modes"], plan: WorkspacePlan, add: AddPlanFile, unsupported: (d: Unsupported["dimension"], r: string) => void, mcpAliases?: readonly string[]): void;
2
- import type { AgentProfile, AgentProfilePermission } from "@tangle-network/agent-interface";
3
- import type { HarnessId } from "./harness.js";
4
- import type { AddPlanFile } from "./plan-json.js";
5
- import type { Unsupported, WorkspacePlan } from "./plan-types.js";
@@ -1,199 +0,0 @@
1
- export function materializeExecutionControls(h, tools, permissions, modes, plan, add, unsupported, mcpAliases = []) {
2
- const toolEntries = Object.entries(tools ?? {}).sort(([left], [right]) => compareStrings(left, right));
3
- const permissionEntries = Object.entries(permissions ?? {}).sort(([left], [right]) => compareStrings(left, right));
4
- switch (h) {
5
- case "claude-code":
6
- materializeClaudeControls(toolEntries, permissionEntries, plan, add, unsupported);
7
- break;
8
- case "opencode":
9
- materializeOpenCodeControls(toolEntries, permissionEntries, add, unsupported, mcpAliases);
10
- break;
11
- case "codex":
12
- materializeCodexControls(toolEntries, permissionEntries, plan, unsupported);
13
- break;
14
- case "pi":
15
- materializePiControls(toolEntries, permissionEntries, plan, add, unsupported);
16
- break;
17
- default:
18
- if (toolEntries.length > 0) {
19
- unsupported("tools", `${h}: no verified primary-process tool availability mapping`);
20
- }
21
- if (permissionEntries.length > 0) {
22
- unsupported("permissions", `${h}: no verified primary-process permission policy mapping`);
23
- }
24
- }
25
- if (modes && Object.keys(modes).length > 0) {
26
- unsupported("modes", `${h}: resolve one named AgentProfile mode before materialization`);
27
- }
28
- }
29
- function materializeClaudeControls(toolEntries, permissionEntries, plan, add, unsupported) {
30
- if (toolEntries.length > 0 || permissionEntries.length > 0) {
31
- const nativeTools = mapSupportedBooleanEntries(toolEntries, claudeToolName, (name) => unsupported("tools", `claude-code: no verified native tool mapping for "${name}". Accepted: ${CLAUDE_CODE_NATIVE_TOOL_IDS.join(", ")}`));
32
- const nativePermissions = mapSupportedPermissionEntries(permissionEntries, claudeToolName, (name) => unsupported("permissions", `claude-code: no verified native permission mapping for "${name}". Accepted: ${CLAUDE_CODE_NATIVE_PERMISSION_IDS.join(", ")}`), "claude-code permission");
33
- const rules = claudePermissionRules(nativePermissions);
34
- for (const [tool, enabled] of nativeTools) {
35
- rules[enabled ? "allow" : "deny"].push(tool);
36
- }
37
- add(CLAUDE_SETTINGS_PATH, serializeWorkspacePlanJson({
38
- permissions: sealClaudePermissionRules(rules),
39
- }));
40
- registerClaudeSettings(plan);
41
- }
42
- }
43
- function materializeOpenCodeControls(toolEntries, permissionEntries, add, unsupported, mcpAliases) {
44
- const enforceableTools = toolEntries.filter(([name, enabled]) => {
45
- if (normalizedControlName(name) !== "lsp" || !enabled)
46
- return true;
47
- unsupported("tools", "opencode: the LSP tool is disabled by the runtime and cannot be enabled exactly");
48
- return false;
49
- });
50
- const enforceablePermissions = permissionEntries.filter(([name, policy]) => {
51
- if (normalizedControlName(name) !== "read")
52
- return true;
53
- if (!permissionAllowsEverything(policy)) {
54
- unsupported("permissions", "opencode: read access cannot be denied or approval-gated");
55
- }
56
- return false;
57
- });
58
- const nativeTools = mapSupportedBooleanEntries(enforceableTools, openCodeToolName, (name) => unsupported("tools", `opencode: no verified native tool mapping for "${name}". Accepted: ${OPENCODE_NATIVE_TOOL_IDS.join(", ")}`));
59
- const nativePermissions = mapSupportedPermissionEntries(enforceablePermissions, (name) => openCodeDeclaredMcpPermissionName(name, mcpAliases) ??
60
- openCodePermissionName(name), (name) => unsupported("permissions", `opencode: no verified native permission mapping for "${name}". Accepted: ${OPENCODE_NATIVE_PERMISSION_IDS.join(", ")}`), "opencode permission");
61
- if (enforceableTools.length > 0 || permissionEntries.length > 0) {
62
- add(OPENCODE_CONFIG_FILE, serializeWorkspacePlanJson({
63
- $schema: OPENCODE_CONFIG_SCHEMA,
64
- ...(nativeTools.length > 0
65
- ? { tools: Object.fromEntries(nativeTools) }
66
- : {}),
67
- ...(nativePermissions.length > 0
68
- ? { permission: Object.fromEntries(nativePermissions) }
69
- : {}),
70
- }));
71
- }
72
- }
73
- function materializeCodexControls(toolEntries, permissionEntries, plan, unsupported) {
74
- const searchEntries = [];
75
- const fetchEntries = [];
76
- for (const [name, enabled] of toolEntries) {
77
- const posture = resolveNativeWebTools({ [name]: enabled });
78
- if (posture.search === enabled && posture.fetch === undefined) {
79
- searchEntries.push([name, enabled]);
80
- continue;
81
- }
82
- if (posture.fetch === enabled && posture.search === undefined) {
83
- fetchEntries.push([name, enabled]);
84
- continue;
85
- }
86
- unsupported("tools", `codex: no verified primary-process tool mapping for "${name}"`);
87
- }
88
- // Codex has no native fetch: only a fetch that stays on after its
89
- // spellings merge asks for something this harness cannot run.
90
- const nativeFetch = mapSupportedBooleanEntries(fetchEntries, () => "web_fetch", () => undefined);
91
- if (nativeFetch[0]?.[1]) {
92
- for (const [name] of fetchEntries) {
93
- unsupported("tools", `codex: native web fetch is unavailable for "${name}"`);
94
- }
95
- }
96
- const nativeSearch = mapSupportedBooleanEntries(searchEntries, () => "web_search", () => undefined);
97
- if (nativeSearch[0]) {
98
- plan.flags.push("-c", `tools.web_search=${nativeSearch[0][1]}`);
99
- }
100
- if (permissionEntries.length > 0) {
101
- unsupported("permissions", "codex: no verified primary-process permission policy mapping");
102
- }
103
- }
104
- function materializePiControls(toolEntries, permissionEntries, plan, add, unsupported) {
105
- const nativeTools = mapSupportedBooleanEntries(toolEntries, piToolId, (name) => unsupported("tools", `pi: tool name "${name}" is empty after trimming`));
106
- const nativePermissions = mapSupportedPermissionEntries(permissionEntries, piToolId, (name) => unsupported("permissions", `pi: permission name "${name}" is empty after trimming`), "pi permission");
107
- const enabled = new Set(nativeTools
108
- .filter(([, value]) => value)
109
- .map(([name]) => name)
110
- .sort(compareStrings));
111
- const excluded = new Set(nativeTools
112
- .filter(([, value]) => !value)
113
- .map(([name]) => name)
114
- .sort(compareStrings));
115
- for (const [name, policy] of nativePermissions) {
116
- if (typeof policy !== "string") {
117
- unsupported("permissions", `pi: conditional permission policy for "${name}" has no verified headless mapping`);
118
- continue;
119
- }
120
- if (policy === "ask") {
121
- unsupported("permissions", `pi: permission policy "ask" for "${name}" requires an interactive decision surface`);
122
- continue;
123
- }
124
- // The stricter control holds, as across spellings of one tool: an
125
- // allow keeps a disabled tool disabled, and a deny excludes an
126
- // enabled one.
127
- if (policy === "allow")
128
- continue;
129
- enabled.delete(name);
130
- excluded.add(name);
131
- }
132
- if (enabled.size > 0 || excluded.size > 0) {
133
- add(PI_EXECUTION_CONTROL_EXTENSION_PATH, piExecutionControlExtensionSource([...enabled], [...excluded]));
134
- plan.flags.push("--extension", PI_EXECUTION_CONTROL_EXTENSION_PATH);
135
- }
136
- if (excluded.size > 0) {
137
- plan.flags.push("--exclude-tools", [...excluded].sort(compareStrings).join(","));
138
- }
139
- }
140
- function piExecutionControlExtensionSource(enabledTools, excludedTools) {
141
- return [
142
- 'import { writeSync } from "node:fs";',
143
- `const enabledTools = Object.freeze(${JSON.stringify(enabledTools)});`,
144
- `const excludedTools = Object.freeze(${JSON.stringify(excludedTools)});`,
145
- "",
146
- "function failProfile(message) {",
147
- " try {",
148
- ' writeSync(2, "Pi profile execution controls failed: " + message + "\\n");',
149
- " } finally {",
150
- " process.exit(78);",
151
- " }",
152
- "}",
153
- "",
154
- "function resolveTool(reference, available, required) {",
155
- " if (available.includes(reference)) return reference;",
156
- " const folded = reference.toLowerCase();",
157
- " const matches = available.filter((name) => name.toLowerCase() === folded);",
158
- " if (matches.length === 1) return matches[0];",
159
- " if (matches.length > 1) {",
160
- " failProfile('tool \"' + reference + '\" is ambiguous: ' + matches.join(\", \"));",
161
- " }",
162
- " if (required) {",
163
- " failProfile('requested tool \"' + reference + '\" is unavailable');",
164
- " }",
165
- " return undefined;",
166
- "}",
167
- "",
168
- "function applyControls(pi) {",
169
- " const available = pi.getAllTools().map((tool) => tool.name);",
170
- " for (const reference of excludedTools) {",
171
- " const remaining = resolveTool(reference, available, false);",
172
- " if (remaining !== undefined) {",
173
- " failProfile(",
174
- " 'excluded tool \"' + reference + '\" remains available as \"' + remaining +",
175
- " '\"; pass its exact registered Pi name',",
176
- " );",
177
- " }",
178
- " }",
179
- " const enabled = enabledTools.map((name) =>",
180
- " resolveTool(name, available, true),",
181
- " );",
182
- " const active = new Set(pi.getActiveTools());",
183
- " for (const name of enabled) active.add(name);",
184
- " pi.setActiveTools([...active]);",
185
- "}",
186
- "",
187
- "export default function applyProfileExecutionControls(pi) {",
188
- ' pi.on("before_agent_start", () => applyControls(pi));',
189
- ' pi.on("turn_start", () => applyControls(pi));',
190
- "}",
191
- "",
192
- ].join("\n");
193
- }
194
- import { resolveNativeWebTools } from "@tangle-network/agent-interface";
195
- import { CLAUDE_CODE_NATIVE_PERMISSION_IDS, CLAUDE_CODE_NATIVE_TOOL_IDS, claudePermissionRules, claudeToolName, mapSupportedBooleanEntries, mapSupportedPermissionEntries, normalizedControlName, OPENCODE_NATIVE_PERMISSION_IDS, OPENCODE_NATIVE_TOOL_IDS, openCodeDeclaredMcpPermissionName, openCodePermissionName, openCodeToolName, PI_EXECUTION_CONTROL_EXTENSION_PATH, permissionAllowsEverything, piToolId, sealClaudePermissionRules, } from "./control-maps.js";
196
- import { CLAUDE_SETTINGS_PATH, OPENCODE_CONFIG_FILE, OPENCODE_CONFIG_SCHEMA, } from "./harness.js";
197
- import { registerClaudeSettings } from "./hooks.js";
198
- import { serializeWorkspacePlanJson } from "./plan-json.js";
199
- import { compareStrings } from "./workspace-files.js";
@@ -1,22 +0,0 @@
1
- import type { AgentProfile } from "@tangle-network/agent-interface";
2
- import type { HarnessId } from "./harness.js";
3
- /** Keep controlled Codex launches on declared tools, not account apps or plugins. */
4
- export declare const CODEX_DISABLED_FEATURE_ARGS: readonly string[];
5
- /**
6
- * Whether a run keeps the harness's own system prompt, so that a host's
7
- * environment context joins the profile's addition instead of replacing the
8
- * harness prompt.
9
- *
10
- * A profile that declares prompt intent (`prompt.instructions` or
11
- * `prompt.appendSystemPrompt`) without `prompt.systemPrompt` asked for the
12
- * harness prompt to stay in force: that is what `appendSystemPrompt` means.
13
- * A host that replaced it with its own template would delete exactly what the
14
- * profile kept. Every Discovery research director ran under the
15
- * sandbox's app-builder template this way: claude-code through
16
- * `--system-prompt`, opencode through a generated primary agent.
17
- */
18
- export declare function keepsHarnessSystemPrompt(profile: AgentProfile | undefined, harness: HarnessId | string | undefined): boolean;
19
- /** Join prompt additions in order, skipping empty ones. */
20
- export declare function joinSystemPromptAdditions(...additions: Array<string | undefined>): string | undefined;
21
- /** Native inline context has a bounded argument; long context belongs in resources.files. */
22
- export declare function codexDeveloperInstructionArgs(value: string | undefined, flags?: readonly string[]): string[];
@@ -1,64 +0,0 @@
1
- /** Keep controlled Codex launches on declared tools, not account apps or plugins. */
2
- export const CODEX_DISABLED_FEATURE_ARGS = [
3
- "apps",
4
- "plugins",
5
- ].flatMap((feature) => ["-c", `features.${feature}=false`]);
6
- /**
7
- * Harnesses whose adapter can carry a host's environment context as an
8
- * ADDITION to the harness's own system prompt:
9
- *
10
- * - claude-code and pi: `--append-system-prompt`.
11
- * - opencode: the addition file that opencode.json names in `instructions`
12
- * (OPENCODE_APPEND_SYSTEM_PROMPT_FILE). OpenCode's own prompt stays because
13
- * no profile primary agent replaces it.
14
- * - codex: `developer_instructions`, which codex sends after its own base
15
- * instructions.
16
- *
17
- * Every other harness keeps the host's replacement, unchanged.
18
- */
19
- const HARNESSES_WITH_ADDITIVE_HOST_PROMPT = new Set(["claude-code", "pi", "opencode", "codex"]);
20
- /**
21
- * Whether a run keeps the harness's own system prompt, so that a host's
22
- * environment context joins the profile's addition instead of replacing the
23
- * harness prompt.
24
- *
25
- * A profile that declares prompt intent (`prompt.instructions` or
26
- * `prompt.appendSystemPrompt`) without `prompt.systemPrompt` asked for the
27
- * harness prompt to stay in force: that is what `appendSystemPrompt` means.
28
- * A host that replaced it with its own template would delete exactly what the
29
- * profile kept. Every Discovery research director ran under the
30
- * sandbox's app-builder template this way: claude-code through
31
- * `--system-prompt`, opencode through a generated primary agent.
32
- */
33
- export function keepsHarnessSystemPrompt(profile, harness) {
34
- const prompt = profile?.prompt;
35
- if (!profile || prompt?.systemPrompt !== undefined)
36
- return false;
37
- return (HARNESSES_WITH_ADDITIVE_HOST_PROMPT.has(harness ?? "") &&
38
- ((prompt?.instructions?.length ?? 0) > 0 ||
39
- prompt?.appendSystemPrompt !== undefined ||
40
- (harness === "codex" && profile.resources?.instructions !== undefined)));
41
- }
42
- /** Join prompt additions in order, skipping empty ones. */
43
- export function joinSystemPromptAdditions(...additions) {
44
- const parts = additions.map((addition) => addition?.trim()).filter(Boolean);
45
- return parts.join("\n\n") || undefined;
46
- }
47
- /** Native inline context has a bounded argument; long context belongs in resources.files. */
48
- export function codexDeveloperInstructionArgs(value, flags = []) {
49
- if (value === undefined)
50
- return [];
51
- if (flags.some((flag, index) => {
52
- const selector = ["-c", "--config"].includes(flags[index - 1] ?? "")
53
- ? flag
54
- : /^(?:--config=|-c=?)(.+)$/su.exec(flag)?.[1];
55
- return (selector &&
56
- /^(?:developer_instructions\s*=|["'])/u.test(selector.trimStart()));
57
- }))
58
- throw new Error("Codex context conflicts with a caller configuration override; put developer_instructions in the AgentProfile and use unquoted configuration keys");
59
- const encoded = `developer_instructions=${JSON.stringify(value)}`;
60
- // This is the adapter's portable inline budget, not an OS capacity claim.
61
- if (Buffer.byteLength(encoded, "utf8") > 64 * 1024)
62
- throw new Error("Codex inline context is too large; move long context into profile.resources.files and keep prompt.instructions short");
63
- return ["-c", encoded];
64
- }