@tangle-network/agent-profile-materialize 0.17.3 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/harness.d.ts +18 -3
- package/dist/harness.js +55 -3
- package/dist/profile-plan.js +2 -2
- package/dist/profile-prompt.js +2 -2
- package/dist/subagents.js +9 -5
- package/dist/workspace-apply.js +1 -1
- package/package.json +3 -3
package/dist/harness.d.ts
CHANGED
|
@@ -72,12 +72,27 @@ export declare const MATERIALIZER_RESERVED_PATHS: ReadonlySet<string>;
|
|
|
72
72
|
* one is worse than refusing the turn.
|
|
73
73
|
*/
|
|
74
74
|
export declare const MATERIALIZER_OWNED_PATHS: ReadonlySet<string>;
|
|
75
|
+
/** Mark one generated native agent file without changing its harness meaning. */
|
|
76
|
+
export declare function markMaterializerSubagent(relPath: string, content: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Whether a generated file occupies a path that the profile materializer owns.
|
|
79
|
+
*
|
|
80
|
+
* Generated native agent files come from the active profile.
|
|
81
|
+
* The active profile must replace the prior profile version at the same path.
|
|
82
|
+
* Otherwise, a profile update stops every resumed session before launch.
|
|
83
|
+
* Explicit resources at these paths stay shared.
|
|
84
|
+
* The plan builder applies ownership only when the source is `generated`.
|
|
85
|
+
* An output marker proves ownership before replacement.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isMaterializerOwnedPath(relPath: string): boolean;
|
|
75
88
|
/** The top-level keys this package writes into `opencode.json`. */
|
|
76
89
|
export declare const OPENCODE_CONFIG_KEYS: ReadonlySet<string>;
|
|
77
90
|
/**
|
|
78
91
|
* Whether the file already at owned path `relPath` is this package's own
|
|
79
|
-
* earlier output. A reserved name proves that by itself
|
|
80
|
-
*
|
|
92
|
+
* earlier output. A reserved name proves that by itself. Native agent files
|
|
93
|
+
* require the generated marker. An exact unmarked copy of the planned file is
|
|
94
|
+
* adopted once for upgrades from materializer versions without that marker.
|
|
95
|
+
* `undefined` content means no regular file is there.
|
|
81
96
|
*
|
|
82
97
|
* An `opencode.json` this package wrote holds the opencode schema id, only the
|
|
83
98
|
* keys this package writes, and only instruction paths this package invented.
|
|
@@ -85,7 +100,7 @@ export declare const OPENCODE_CONFIG_KEYS: ReadonlySet<string>;
|
|
|
85
100
|
* and is replaced too; a config with any other key, or with an instruction path
|
|
86
101
|
* of the user's own, is refused.
|
|
87
102
|
*/
|
|
88
|
-
export declare function isMaterializerOutput(relPath: string, content: () => string | undefined): boolean;
|
|
103
|
+
export declare function isMaterializerOutput(relPath: string, content: () => string | undefined, plannedContent?: string): boolean;
|
|
89
104
|
/**
|
|
90
105
|
* cwd-relative native skill dir per harness, or null if the harness has no
|
|
91
106
|
* cwd skill primitive (skills live in a user/global dir the workspace plan
|
package/dist/harness.js
CHANGED
|
@@ -142,6 +142,40 @@ export const MATERIALIZER_OWNED_PATHS = new Set([
|
|
|
142
142
|
...MATERIALIZER_RESERVED_PATHS,
|
|
143
143
|
OPENCODE_CONFIG_FILE,
|
|
144
144
|
]);
|
|
145
|
+
const MATERIALIZER_SUBAGENT_PATH = /^(?:(?:\.claude|\.opencode|\.gemini)\/agents\/[a-z0-9][a-z0-9_-]*\.md|\.codex\/agents\/[a-z0-9][a-z0-9_-]*\.toml)$/u;
|
|
146
|
+
const MATERIALIZER_SUBAGENT_MARKER = "# tangle-agent-profile-materialize: generated";
|
|
147
|
+
function materializerSubagentMarkerPrefix(relPath) {
|
|
148
|
+
return relPath.endsWith(".toml")
|
|
149
|
+
? `${MATERIALIZER_SUBAGENT_MARKER}\n`
|
|
150
|
+
: `---\n${MATERIALIZER_SUBAGENT_MARKER}\n`;
|
|
151
|
+
}
|
|
152
|
+
/** Mark one generated native agent file without changing its harness meaning. */
|
|
153
|
+
export function markMaterializerSubagent(relPath, content) {
|
|
154
|
+
if (!MATERIALIZER_SUBAGENT_PATH.test(relPath)) {
|
|
155
|
+
throw new Error(`Not a native agent path: ${relPath}`);
|
|
156
|
+
}
|
|
157
|
+
if (relPath.endsWith(".toml")) {
|
|
158
|
+
return `${materializerSubagentMarkerPrefix(relPath)}${content}`;
|
|
159
|
+
}
|
|
160
|
+
if (!content.startsWith("---\n")) {
|
|
161
|
+
throw new Error(`Native Markdown agent has no frontmatter: ${relPath}`);
|
|
162
|
+
}
|
|
163
|
+
return `${materializerSubagentMarkerPrefix(relPath)}${content.slice(4)}`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Whether a generated file occupies a path that the profile materializer owns.
|
|
167
|
+
*
|
|
168
|
+
* Generated native agent files come from the active profile.
|
|
169
|
+
* The active profile must replace the prior profile version at the same path.
|
|
170
|
+
* Otherwise, a profile update stops every resumed session before launch.
|
|
171
|
+
* Explicit resources at these paths stay shared.
|
|
172
|
+
* The plan builder applies ownership only when the source is `generated`.
|
|
173
|
+
* An output marker proves ownership before replacement.
|
|
174
|
+
*/
|
|
175
|
+
export function isMaterializerOwnedPath(relPath) {
|
|
176
|
+
return (MATERIALIZER_OWNED_PATHS.has(relPath) ||
|
|
177
|
+
MATERIALIZER_SUBAGENT_PATH.test(relPath));
|
|
178
|
+
}
|
|
145
179
|
/** The top-level keys this package writes into `opencode.json`. */
|
|
146
180
|
export const OPENCODE_CONFIG_KEYS = new Set([
|
|
147
181
|
"$schema",
|
|
@@ -152,8 +186,10 @@ export const OPENCODE_CONFIG_KEYS = new Set([
|
|
|
152
186
|
]);
|
|
153
187
|
/**
|
|
154
188
|
* Whether the file already at owned path `relPath` is this package's own
|
|
155
|
-
* earlier output. A reserved name proves that by itself
|
|
156
|
-
*
|
|
189
|
+
* earlier output. A reserved name proves that by itself. Native agent files
|
|
190
|
+
* require the generated marker. An exact unmarked copy of the planned file is
|
|
191
|
+
* adopted once for upgrades from materializer versions without that marker.
|
|
192
|
+
* `undefined` content means no regular file is there.
|
|
157
193
|
*
|
|
158
194
|
* An `opencode.json` this package wrote holds the opencode schema id, only the
|
|
159
195
|
* keys this package writes, and only instruction paths this package invented.
|
|
@@ -161,9 +197,25 @@ export const OPENCODE_CONFIG_KEYS = new Set([
|
|
|
161
197
|
* and is replaced too; a config with any other key, or with an instruction path
|
|
162
198
|
* of the user's own, is refused.
|
|
163
199
|
*/
|
|
164
|
-
export function isMaterializerOutput(relPath, content) {
|
|
200
|
+
export function isMaterializerOutput(relPath, content, plannedContent) {
|
|
165
201
|
if (MATERIALIZER_RESERVED_PATHS.has(relPath))
|
|
166
202
|
return true;
|
|
203
|
+
if (MATERIALIZER_SUBAGENT_PATH.test(relPath)) {
|
|
204
|
+
const existing = content();
|
|
205
|
+
if (existing === undefined)
|
|
206
|
+
return false;
|
|
207
|
+
const markerPrefix = materializerSubagentMarkerPrefix(relPath);
|
|
208
|
+
if (existing.startsWith(markerPrefix))
|
|
209
|
+
return true;
|
|
210
|
+
if (plannedContent === undefined)
|
|
211
|
+
return false;
|
|
212
|
+
if (!plannedContent.startsWith(markerPrefix))
|
|
213
|
+
return false;
|
|
214
|
+
const legacyContent = relPath.endsWith(".toml")
|
|
215
|
+
? plannedContent.slice(markerPrefix.length)
|
|
216
|
+
: `---\n${plannedContent.slice(markerPrefix.length)}`;
|
|
217
|
+
return existing === legacyContent;
|
|
218
|
+
}
|
|
167
219
|
if (relPath !== OPENCODE_CONFIG_FILE)
|
|
168
220
|
return false;
|
|
169
221
|
const bytes = content();
|
package/dist/profile-plan.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isMaterializerOwnedPath, MATERIALIZER_RESERVED_PATHS, } from "./harness.js";
|
|
2
2
|
import { workspacePlanConfigValue } from "./plan-json.js";
|
|
3
3
|
import { mergeJsonFiles } from "./plan-normalize.js";
|
|
4
4
|
import { formatUnsupported } from "./plan-support.js";
|
|
@@ -24,7 +24,7 @@ export function createProfilePlanBuilder(harness) {
|
|
|
24
24
|
relPath: normalizedPath,
|
|
25
25
|
content,
|
|
26
26
|
source,
|
|
27
|
-
...(source === "generated" &&
|
|
27
|
+
...(source === "generated" && isMaterializerOwnedPath(normalizedPath)
|
|
28
28
|
? { ownership: "materializer" }
|
|
29
29
|
: {}),
|
|
30
30
|
...(fileMode === undefined ? {} : { mode: validateFileMode(fileMode) }),
|
package/dist/profile-prompt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendSystemPromptRefusal, CODEX_SYSTEM_PROMPT_FILE, CONTEXT_FILE, OPENCODE_APPEND_SYSTEM_PROMPT_FILE, OPENCODE_CONFIG_FILE, OPENCODE_CONFIG_SCHEMA, OPENCODE_PRIMARY_AGENT_PREFIX, systemPromptRefusal, } from "./harness.js";
|
|
1
|
+
import { appendSystemPromptRefusal, CODEX_SYSTEM_PROMPT_FILE, CONTEXT_FILE, markMaterializerSubagent, OPENCODE_APPEND_SYSTEM_PROMPT_FILE, OPENCODE_CONFIG_FILE, OPENCODE_CONFIG_SCHEMA, OPENCODE_PRIMARY_AGENT_PREFIX, systemPromptRefusal, } from "./harness.js";
|
|
2
2
|
import { sha256 } from "./plan-digest.js";
|
|
3
3
|
import { serializeWorkspacePlanJson } from "./plan-json.js";
|
|
4
4
|
export function lowerProfilePrompt(profile, harness, binds, builder) {
|
|
@@ -34,7 +34,7 @@ export function lowerProfilePrompt(profile, harness, binds, builder) {
|
|
|
34
34
|
throw new Error(`opencode: subagent name "${primaryAgentName}" is reserved for the profile primary agent`);
|
|
35
35
|
}
|
|
36
36
|
plan.systemPrompt = systemPrompt;
|
|
37
|
-
add(primaryAgentPath, ["---", "mode: primary", "---", systemPrompt, ""].join("\n"));
|
|
37
|
+
add(primaryAgentPath, markMaterializerSubagent(primaryAgentPath, ["---", "mode: primary", "---", systemPrompt, ""].join("\n")));
|
|
38
38
|
plan.flags.push("--agent", primaryAgentName);
|
|
39
39
|
}
|
|
40
40
|
else {
|
package/dist/subagents.js
CHANGED
|
@@ -27,7 +27,8 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
27
27
|
fm.push(`maxTurns: ${sa.maxSteps}`);
|
|
28
28
|
reportUnsupportedSubagentFields(h, name, sa, new Set(["description", "prompt", "model", "tools", "maxSteps"]), unsupported);
|
|
29
29
|
fm.push("---", "", sa.prompt ?? sa.description ?? name);
|
|
30
|
-
|
|
30
|
+
const relPath = `.claude/agents/${name}.md`;
|
|
31
|
+
add(relPath, markMaterializerSubagent(relPath, `${fm.join("\n")}\n`));
|
|
31
32
|
break;
|
|
32
33
|
}
|
|
33
34
|
case "opencode": {
|
|
@@ -53,7 +54,8 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
53
54
|
"maxSteps",
|
|
54
55
|
]), unsupported);
|
|
55
56
|
fm.push("---", "", sa.prompt ?? "");
|
|
56
|
-
|
|
57
|
+
const relPath = `.opencode/agents/${name}.md`;
|
|
58
|
+
add(relPath, markMaterializerSubagent(relPath, `${fm.join("\n")}\n`));
|
|
57
59
|
break;
|
|
58
60
|
}
|
|
59
61
|
case "gemini": {
|
|
@@ -63,7 +65,8 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
63
65
|
.map(([tool]) => tool)
|
|
64
66
|
: undefined;
|
|
65
67
|
reportUnsupportedSubagentFields(h, name, sa, new Set(["description", "prompt", "model", "tools", "maxSteps"]), unsupported);
|
|
66
|
-
|
|
68
|
+
const relPath = `.gemini/agents/${name}.md`;
|
|
69
|
+
add(relPath, markMaterializerSubagent(relPath, [
|
|
67
70
|
"---",
|
|
68
71
|
`name: ${name}`,
|
|
69
72
|
`description: ${JSON.stringify(sa.description ?? name)}`,
|
|
@@ -75,7 +78,7 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
75
78
|
"",
|
|
76
79
|
sa.prompt ?? "",
|
|
77
80
|
"",
|
|
78
|
-
].join("\n"));
|
|
81
|
+
].join("\n")));
|
|
79
82
|
break;
|
|
80
83
|
}
|
|
81
84
|
case "codex": {
|
|
@@ -88,7 +91,7 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
88
91
|
if (sa.model)
|
|
89
92
|
lines.push(`model = ${tomlStr(sa.model)}`);
|
|
90
93
|
reportUnsupportedSubagentFields(h, name, sa, new Set(["description", "prompt", "model"]), unsupported);
|
|
91
|
-
add(relPath, `${lines.join("\n")}\n`);
|
|
94
|
+
add(relPath, markMaterializerSubagent(relPath, `${lines.join("\n")}\n`));
|
|
92
95
|
registerCodexAgent(plan, name, relPath, sa.description ?? name);
|
|
93
96
|
break;
|
|
94
97
|
}
|
|
@@ -130,4 +133,5 @@ function reportUnsupportedSubagentFields(harness, name, subagent, supported, uns
|
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
}
|
|
136
|
+
import { markMaterializerSubagent } from "./harness.js";
|
|
133
137
|
import { tomlStr } from "./plan-json.js";
|
package/dist/workspace-apply.js
CHANGED
|
@@ -62,7 +62,7 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
62
62
|
// the workspace, not authorship.
|
|
63
63
|
const mayReplace = (file) => existingFiles === "allow-identical" &&
|
|
64
64
|
file.ownership === "materializer" &&
|
|
65
|
-
isMaterializerOutput(file.relPath, () => regularFileContent(file.absPath));
|
|
65
|
+
isMaterializerOutput(file.relPath, () => regularFileContent(file.absPath), file.content);
|
|
66
66
|
// Validate the complete plan before the first write so a late traversal or
|
|
67
67
|
// existing symlink cannot leave a partially materialized workspace.
|
|
68
68
|
for (const file of filesToApply) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
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",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@tangle-network/agent-interface": "
|
|
23
|
+
"@tangle-network/agent-interface": "2.0.0",
|
|
24
24
|
"@types/node": "25.6.0",
|
|
25
25
|
"typescript": "^6.0.3",
|
|
26
26
|
"vitest": "^4.1.10"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@tangle-network/agent-interface": "^1.0.0"
|
|
29
|
+
"@tangle-network/agent-interface": "^1.0.0 || ^2.0.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsc -p tsconfig.json",
|