@tangle-network/agent-profile-materialize 0.26.7 → 0.26.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.
package/dist/control-maps.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export declare function normalizedControlName(name: string): string;
|
|
2
|
+
/** Tool IDs accepted by Claude Code. */
|
|
3
|
+
export declare const CLAUDE_CODE_NATIVE_TOOL_IDS: readonly string[];
|
|
4
|
+
/** Permission IDs accepted by Claude Code (same as tool IDs). */
|
|
5
|
+
export declare const CLAUDE_CODE_NATIVE_PERMISSION_IDS: readonly string[];
|
|
2
6
|
export declare function claudeToolName(name: string): string | undefined;
|
|
3
7
|
/** Tool IDs reported by the OpenCode binary shipped in the Sandbox image. */
|
|
4
8
|
export declare const OPENCODE_NATIVE_TOOL_IDS: readonly ["apply_patch", "bash", "codesearch", "edit", "glob", "grep", "question", "read", "skill", "task", "todowrite", "webfetch", "websearch", "write"];
|
|
9
|
+
/** Permission IDs accepted by OpenCode (tools except read, plus special permissions). */
|
|
10
|
+
export declare const OPENCODE_NATIVE_PERMISSION_IDS: readonly ["apply_patch", "bash", "codesearch", "doom_loop", "edit", "external_directory", "glob", "grep", "mcp", "question", "skill", "task", "todowrite", "webfetch", "websearch", "write"];
|
|
5
11
|
/** Resolve portable and legacy profile names to current OpenCode tool IDs. */
|
|
6
12
|
export declare function openCodeNativeToolId(name: string): string | undefined;
|
|
7
13
|
export declare function openCodeToolName(name: string): string | undefined;
|
package/dist/control-maps.js
CHANGED
|
@@ -26,6 +26,10 @@ const CLAUDE_TOOL_NAMES = {
|
|
|
26
26
|
websearch: "WebSearch",
|
|
27
27
|
write: "Write",
|
|
28
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;
|
|
29
33
|
export function claudeToolName(name) {
|
|
30
34
|
const web = nativeWebToolKind(name);
|
|
31
35
|
if (web === "search")
|
|
@@ -52,6 +56,25 @@ export const OPENCODE_NATIVE_TOOL_IDS = [
|
|
|
52
56
|
"write",
|
|
53
57
|
];
|
|
54
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
|
+
];
|
|
55
78
|
/** Resolve portable and legacy profile names to current OpenCode tool IDs. */
|
|
56
79
|
export function openCodeNativeToolId(name) {
|
|
57
80
|
const web = nativeWebToolKind(name);
|
|
@@ -4,8 +4,8 @@ export function materializeExecutionControls(h, tools, permissions, modes, plan,
|
|
|
4
4
|
switch (h) {
|
|
5
5
|
case "claude-code": {
|
|
6
6
|
if (toolEntries.length > 0 || permissionEntries.length > 0) {
|
|
7
|
-
const nativeTools = mapSupportedBooleanEntries(toolEntries, claudeToolName, (name) => unsupported("tools", `claude-code: no verified native tool mapping for "${name}"`));
|
|
8
|
-
const nativePermissions = mapSupportedPermissionEntries(permissionEntries, claudeToolName, (name) => unsupported("permissions", `claude-code: no verified native permission mapping for "${name}"`), "claude-code permission");
|
|
7
|
+
const nativeTools = mapSupportedBooleanEntries(toolEntries, claudeToolName, (name) => unsupported("tools", `claude-code: no verified native tool mapping for "${name}". Accepted: ${CLAUDE_CODE_NATIVE_TOOL_IDS.join(", ")}`));
|
|
8
|
+
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");
|
|
9
9
|
const rules = claudePermissionRules(nativePermissions);
|
|
10
10
|
for (const [tool, enabled] of nativeTools) {
|
|
11
11
|
rules[enabled ? "allow" : "deny"].push(tool);
|
|
@@ -32,9 +32,9 @@ export function materializeExecutionControls(h, tools, permissions, modes, plan,
|
|
|
32
32
|
}
|
|
33
33
|
return false;
|
|
34
34
|
});
|
|
35
|
-
const nativeTools = mapSupportedBooleanEntries(enforceableTools, openCodeToolName, (name) => unsupported("tools", `opencode: no verified native tool mapping for "${name}"`));
|
|
35
|
+
const nativeTools = mapSupportedBooleanEntries(enforceableTools, openCodeToolName, (name) => unsupported("tools", `opencode: no verified native tool mapping for "${name}". Accepted: ${OPENCODE_NATIVE_TOOL_IDS.join(", ")}`));
|
|
36
36
|
const nativePermissions = mapSupportedPermissionEntries(enforceablePermissions, (name) => openCodeDeclaredMcpPermissionName(name, mcpAliases) ??
|
|
37
|
-
openCodePermissionName(name), (name) => unsupported("permissions", `opencode: no verified native permission mapping for "${name}"`), "opencode permission");
|
|
37
|
+
openCodePermissionName(name), (name) => unsupported("permissions", `opencode: no verified native permission mapping for "${name}". Accepted: ${OPENCODE_NATIVE_PERMISSION_IDS.join(", ")}`), "opencode permission");
|
|
38
38
|
if (enforceableTools.length > 0 || permissionEntries.length > 0) {
|
|
39
39
|
add(OPENCODE_CONFIG_FILE, serializeWorkspacePlanJson({
|
|
40
40
|
$schema: OPENCODE_CONFIG_SCHEMA,
|
|
@@ -184,7 +184,7 @@ function piExecutionControlExtensionSource(enabledTools, excludedTools) {
|
|
|
184
184
|
].join("\n");
|
|
185
185
|
}
|
|
186
186
|
import { resolveNativeWebTools } from "@tangle-network/agent-interface";
|
|
187
|
-
import { claudePermissionRules, claudeToolName, mapSupportedBooleanEntries, mapSupportedPermissionEntries, normalizedControlName, openCodeDeclaredMcpPermissionName, openCodePermissionName, openCodeToolName, PI_EXECUTION_CONTROL_EXTENSION_PATH, permissionAllowsEverything, piToolId, sealClaudePermissionRules, } from "./control-maps.js";
|
|
187
|
+
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";
|
|
188
188
|
import { CLAUDE_SETTINGS_PATH, OPENCODE_CONFIG_FILE, OPENCODE_CONFIG_SCHEMA, } from "./harness.js";
|
|
189
189
|
import { registerClaudeSettings } from "./hooks.js";
|
|
190
190
|
import { serializeWorkspacePlanJson } from "./plan-json.js";
|
|
@@ -13,11 +13,17 @@ export declare function secretSessionViewWorkspace(input: {
|
|
|
13
13
|
}): string;
|
|
14
14
|
/**
|
|
15
15
|
* Point a session's view at one published secret revision before the
|
|
16
|
-
* session's turn runs. The view's `reference/`
|
|
17
|
-
* links to the revision's read-only files (the same
|
|
18
|
-
* ACL), so the session reads this turn's files and
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* session's turn runs. The view's `reference/` and `.opencode/skills/` become
|
|
17
|
+
* fresh trees of hard links to the revision's read-only files (the same
|
|
18
|
+
* inodes, owner, mode and ACL), so the session reads this turn's files and
|
|
19
|
+
* skills and nothing the previous revision held. A product's skill index
|
|
20
|
+
* names `.opencode/skills/<id>/SKILL.md` cwd-relative, and OpenCode's skill
|
|
21
|
+
* tool lists the same files through the revision's config directory.
|
|
22
|
+
*
|
|
23
|
+
* The view holds nothing else: no `opencode.json`, and a `.opencode/` with
|
|
24
|
+
* only the revision's digest-attested skills, which the launch does not load
|
|
25
|
+
* as project config (it sets OPENCODE_DISABLE_PROJECT_CONFIG). Profile config
|
|
26
|
+
* stays bound by absolute path.
|
|
21
27
|
*
|
|
22
28
|
* The swap is not atomic for a reader that is already running: the caller
|
|
23
29
|
* runs one turn of a session at a time and binds before that turn spawns.
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { chmodSync, linkSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, } from "node:fs";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { HARNESS_SKILL_DIRS } from "./harness.js";
|
|
4
5
|
import { assertLaunchRoot } from "./managed-workspace.js";
|
|
5
6
|
import { runSetfacl } from "./secret-concurrent-revision.js";
|
|
6
|
-
/** The
|
|
7
|
-
const
|
|
7
|
+
/** The profile's read-only files, which products name cwd-relative. */
|
|
8
|
+
const REFERENCE_ENTRY = "reference";
|
|
9
|
+
/**
|
|
10
|
+
* The profile's mounted skills, at the cwd-relative directory the platform
|
|
11
|
+
* publishes for products to name in their prompts (`.opencode/skills`).
|
|
12
|
+
*/
|
|
13
|
+
const SKILLS_DIR = HARNESS_SKILL_DIRS.opencode;
|
|
14
|
+
const SKILLS_ENTRY = dirname(SKILLS_DIR);
|
|
15
|
+
/** The only cwd-relative content a secret OpenCode session view serves. */
|
|
16
|
+
const VIEW_ENTRIES = new Set([
|
|
17
|
+
REFERENCE_ENTRY,
|
|
18
|
+
SKILLS_ENTRY,
|
|
19
|
+
]);
|
|
8
20
|
/**
|
|
9
21
|
* The stable, sealed cwd of one secret OpenCode session. OpenCode reopens a
|
|
10
22
|
* native session at the directory it was created in, so a session resumed on
|
|
@@ -27,11 +39,17 @@ export function secretSessionViewWorkspace(input) {
|
|
|
27
39
|
}
|
|
28
40
|
/**
|
|
29
41
|
* Point a session's view at one published secret revision before the
|
|
30
|
-
* session's turn runs. The view's `reference/`
|
|
31
|
-
* links to the revision's read-only files (the same
|
|
32
|
-
* ACL), so the session reads this turn's files and
|
|
33
|
-
*
|
|
34
|
-
*
|
|
42
|
+
* session's turn runs. The view's `reference/` and `.opencode/skills/` become
|
|
43
|
+
* fresh trees of hard links to the revision's read-only files (the same
|
|
44
|
+
* inodes, owner, mode and ACL), so the session reads this turn's files and
|
|
45
|
+
* skills and nothing the previous revision held. A product's skill index
|
|
46
|
+
* names `.opencode/skills/<id>/SKILL.md` cwd-relative, and OpenCode's skill
|
|
47
|
+
* tool lists the same files through the revision's config directory.
|
|
48
|
+
*
|
|
49
|
+
* The view holds nothing else: no `opencode.json`, and a `.opencode/` with
|
|
50
|
+
* only the revision's digest-attested skills, which the launch does not load
|
|
51
|
+
* as project config (it sets OPENCODE_DISABLE_PROJECT_CONFIG). Profile config
|
|
52
|
+
* stays bound by absolute path.
|
|
35
53
|
*
|
|
36
54
|
* The swap is not atomic for a reader that is already running: the caller
|
|
37
55
|
* runs one turn of a session at a time and binds before that turn spawns.
|
|
@@ -61,32 +79,57 @@ export function bindSecretSessionView(input) {
|
|
|
61
79
|
throw new Error("Secret session view is not an executor-owned directory");
|
|
62
80
|
}
|
|
63
81
|
}
|
|
64
|
-
if (readdirSync(viewWorkspaceDir).some((name) => name
|
|
82
|
+
if (readdirSync(viewWorkspaceDir).some((name) => !VIEW_ENTRIES.has(name))) {
|
|
65
83
|
throw new Error("Secret session view holds an unexpected entry");
|
|
66
84
|
}
|
|
67
85
|
// Only this function writes a view. An interrupted bind leaves a staged or
|
|
68
86
|
// retired tree beside `workspace`, never inside it.
|
|
69
87
|
clearBesideWorkspace(viewRoot);
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
const staged = new Map();
|
|
89
|
+
const reference = sealedRevisionDirectory(revisionWorkspaceDir, REFERENCE_ENTRY);
|
|
90
|
+
if (reference) {
|
|
91
|
+
const target = besideWorkspace(viewRoot, "stage");
|
|
92
|
+
sealDirectories(linkSealedTree(reference, target), input.subprocessUid);
|
|
93
|
+
staged.set(REFERENCE_ENTRY, target);
|
|
94
|
+
}
|
|
95
|
+
const skills = sealedRevisionDirectory(revisionWorkspaceDir, SKILLS_DIR);
|
|
96
|
+
if (skills) {
|
|
97
|
+
const target = besideWorkspace(viewRoot, "stage");
|
|
98
|
+
mkdirSync(target, { mode: 0o700 });
|
|
99
|
+
const directories = [
|
|
100
|
+
target,
|
|
101
|
+
...linkSealedTree(skills, join(target, basename(SKILLS_DIR))),
|
|
102
|
+
];
|
|
79
103
|
sealDirectories(directories, input.subprocessUid);
|
|
104
|
+
staged.set(SKILLS_ENTRY, target);
|
|
80
105
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
106
|
+
for (const entry of VIEW_ENTRIES) {
|
|
107
|
+
const current = join(viewWorkspaceDir, entry);
|
|
108
|
+
if (lstatIfPresent(current)) {
|
|
109
|
+
renameSync(current, besideWorkspace(viewRoot, "retired"));
|
|
110
|
+
}
|
|
111
|
+
const next = staged.get(entry);
|
|
112
|
+
if (next)
|
|
113
|
+
renameSync(next, current);
|
|
84
114
|
}
|
|
85
|
-
if (staged)
|
|
86
|
-
renameSync(staged, current);
|
|
87
115
|
clearBesideWorkspace(viewRoot);
|
|
88
116
|
sealDirectories([viewRoot, viewWorkspaceDir], input.subprocessUid);
|
|
89
117
|
}
|
|
118
|
+
/** A sealed directory of the published revision, or undefined when absent. */
|
|
119
|
+
function sealedRevisionDirectory(revisionWorkspaceDir, relPath) {
|
|
120
|
+
const source = join(revisionWorkspaceDir, relPath);
|
|
121
|
+
const stat = lstatIfPresent(source);
|
|
122
|
+
if (!stat)
|
|
123
|
+
return undefined;
|
|
124
|
+
if (!isSealedNode(stat, "directory")) {
|
|
125
|
+
throw new Error(`Secret revision ${relPath}/ is not sealed`);
|
|
126
|
+
}
|
|
127
|
+
return source;
|
|
128
|
+
}
|
|
129
|
+
/** A fresh staged or retired path beside the view's `workspace`. */
|
|
130
|
+
function besideWorkspace(viewRoot, kind) {
|
|
131
|
+
return join(viewRoot, `.${kind}-${randomBytes(8).toString("hex")}`);
|
|
132
|
+
}
|
|
90
133
|
function isLaunchWorkspace(launchRoot, workspace, name) {
|
|
91
134
|
return (basename(workspace) === "workspace" &&
|
|
92
135
|
dirname(dirname(workspace)) === launchRoot &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.9",
|
|
4
4
|
"description": "Materialize an AgentProfile into the native per-harness workspace files (the one shared materializer for cli-bridge, agent-container, and VB).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"@tangle-network/agent-interface": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
|
36
36
|
},
|
|
37
37
|
"tangleRelease": {
|
|
38
|
-
"sourceSha": "
|
|
39
|
-
"mergedAt": "2026-10-
|
|
38
|
+
"sourceSha": "be89262c7e1faaedf04926a27d0ecbf17d91a318",
|
|
39
|
+
"mergedAt": "2026-10-19T23:54:34-06:00"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.json",
|