@tangle-network/agent-profile-materialize 1.1.7 → 1.1.8
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/dist/index.d.ts +1226 -29
- package/dist/index.js +4987 -23
- package/package.json +7 -5
- package/dist/candidate-types.d.ts +0 -36
- package/dist/candidate-types.js +0 -1
- package/dist/candidate.d.ts +0 -9
- package/dist/candidate.js +0 -283
- package/dist/commands.d.ts +0 -2
- package/dist/commands.js +0 -32
- package/dist/control-maps.d.ts +0 -58
- package/dist/control-maps.js +0 -277
- package/dist/execution-controls.d.ts +0 -5
- package/dist/execution-controls.js +0 -199
- package/dist/harness-prompt.d.ts +0 -22
- package/dist/harness-prompt.js +0 -64
- package/dist/harness.d.ts +0 -131
- package/dist/harness.js +0 -416
- package/dist/hooks.d.ts +0 -6
- package/dist/hooks.js +0 -250
- package/dist/managed-workspace-bind.d.ts +0 -4
- package/dist/managed-workspace-bind.js +0 -106
- package/dist/managed-workspace-stage.d.ts +0 -25
- package/dist/managed-workspace-stage.js +0 -199
- package/dist/managed-workspace-types.d.ts +0 -38
- package/dist/managed-workspace-types.js +0 -1
- package/dist/managed-workspace.d.ts +0 -31
- package/dist/managed-workspace.js +0 -271
- package/dist/mcp-config-ownership.d.ts +0 -13
- package/dist/mcp-config-ownership.js +0 -79
- package/dist/mcp-opencode.d.ts +0 -25
- package/dist/mcp-opencode.js +0 -52
- package/dist/mcp-timeouts.d.ts +0 -7
- package/dist/mcp-timeouts.js +0 -48
- package/dist/mcp.d.ts +0 -4
- package/dist/mcp.js +0 -246
- package/dist/model-id.d.ts +0 -55
- package/dist/model-id.js +0 -67
- package/dist/pi-models-config.d.ts +0 -32
- package/dist/pi-models-config.js +0 -65
- package/dist/plan-application.d.ts +0 -10
- package/dist/plan-application.js +0 -190
- package/dist/plan-digest.d.ts +0 -18
- package/dist/plan-digest.js +0 -116
- package/dist/plan-json.d.ts +0 -13
- package/dist/plan-json.js +0 -91
- package/dist/plan-normalize.d.ts +0 -12
- package/dist/plan-normalize.js +0 -168
- package/dist/plan-prompt.d.ts +0 -38
- package/dist/plan-prompt.js +0 -49
- package/dist/plan-support.d.ts +0 -9
- package/dist/plan-support.js +0 -22
- package/dist/plan-types.d.ts +0 -341
- package/dist/plan-types.js +0 -105
- package/dist/prime-agent-dir.d.ts +0 -64
- package/dist/prime-agent-dir.js +0 -173
- package/dist/prime-fork.d.ts +0 -155
- package/dist/prime-fork.js +0 -262
- package/dist/prime-profile.d.ts +0 -88
- package/dist/prime-profile.js +0 -297
- package/dist/profile-bundle-cache.d.ts +0 -37
- package/dist/profile-bundle-cache.js +0 -320
- package/dist/profile-bundle-key.d.ts +0 -69
- package/dist/profile-bundle-key.js +0 -62
- package/dist/profile-bundle-lru.d.ts +0 -56
- package/dist/profile-bundle-lru.js +0 -220
- package/dist/profile-materializer.d.ts +0 -24
- package/dist/profile-materializer.js +0 -114
- package/dist/profile-plan.d.ts +0 -16
- package/dist/profile-plan.js +0 -90
- package/dist/profile-prompt.d.ts +0 -4
- package/dist/profile-prompt.js +0 -107
- package/dist/profile-support.d.ts +0 -8
- package/dist/profile-support.js +0 -6
- package/dist/resource-lowering.d.ts +0 -6
- package/dist/resource-lowering.js +0 -103
- package/dist/resource-resolution.d.ts +0 -27
- package/dist/resource-resolution.js +0 -265
- package/dist/secret-concurrent-revision.d.ts +0 -30
- package/dist/secret-concurrent-revision.js +0 -249
- package/dist/secret-session-view.d.ts +0 -25
- package/dist/secret-session-view.js +0 -166
- package/dist/subagents.d.ts +0 -6
- package/dist/subagents.js +0 -137
- package/dist/tool-bin-path.d.ts +0 -23
- package/dist/tool-bin-path.js +0 -61
- package/dist/workspace-apply-files.d.ts +0 -57
- package/dist/workspace-apply-files.js +0 -193
- package/dist/workspace-apply-publish.d.ts +0 -22
- package/dist/workspace-apply-publish.js +0 -178
- package/dist/workspace-apply.d.ts +0 -11
- package/dist/workspace-apply.js +0 -124
- package/dist/workspace-files.d.ts +0 -8
- package/dist/workspace-files.js +0 -46
- package/dist/workspace-launch.d.ts +0 -3
- package/dist/workspace-launch.js +0 -13
package/dist/control-maps.js
DELETED
|
@@ -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";
|
package/dist/harness-prompt.d.ts
DELETED
|
@@ -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[];
|
package/dist/harness-prompt.js
DELETED
|
@@ -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
|
-
}
|