@tangle-network/agent-profile-materialize 0.14.2 → 0.15.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/index.d.ts +10 -0
- package/dist/index.js +60 -2
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -309,6 +309,16 @@ export interface ApplyWorkspacePlanOptions {
|
|
|
309
309
|
runtimeOnlyFiles?: readonly string[];
|
|
310
310
|
/** Existing workspace paths are either exact idempotent inputs or forbidden. */
|
|
311
311
|
existingFiles?: "allow-identical" | "reject";
|
|
312
|
+
/**
|
|
313
|
+
* `workspacePlanDigest` of a plan a prior call already applied into this
|
|
314
|
+
* workspace. When it equals this plan's digest, application writes no files:
|
|
315
|
+
* the workspace holds the running agent's live state, and a file the agent
|
|
316
|
+
* edited or removed since the prior application stays exactly as the agent
|
|
317
|
+
* left it. Env, flags, prompt intents, and the receipt are still produced.
|
|
318
|
+
* A different digest applies normally, so a plan that changed mid-session
|
|
319
|
+
* still meets the existing-file refusal.
|
|
320
|
+
*/
|
|
321
|
+
appliedPlanDigest?: string;
|
|
312
322
|
}
|
|
313
323
|
/** Fail when a profile asks for controls the chosen harness cannot execute. */
|
|
314
324
|
export declare function assertWorkspacePlanSupported(plan: Pick<WorkspacePlan, "unsupported">): void;
|
package/dist/index.js
CHANGED
|
@@ -1894,6 +1894,28 @@ function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDir, opti
|
|
|
1894
1894
|
...file,
|
|
1895
1895
|
absPath: resolveWorkspaceFilePath(workspaceRoot, file.relPath),
|
|
1896
1896
|
}));
|
|
1897
|
+
// A prior application of THIS EXACT plan makes the workspace the running
|
|
1898
|
+
// agent's live state: a file the agent edited or removed since then is the
|
|
1899
|
+
// session's own memory, not a conflict, so a byte comparison against the
|
|
1900
|
+
// plan would refuse a resume the caller proved is unchanged. The digest the
|
|
1901
|
+
// caller passes is its own record of what it already applied here; only the
|
|
1902
|
+
// caller can know that, which is why this is an option and not an inference
|
|
1903
|
+
// from disk. `reject` is not short-circuited: that policy asserts an empty
|
|
1904
|
+
// target and must keep failing on any pre-existing path.
|
|
1905
|
+
if (existingFiles === "allow-identical" &&
|
|
1906
|
+
options.appliedPlanDigest !== undefined &&
|
|
1907
|
+
options.appliedPlanDigest === publicPlanDigest) {
|
|
1908
|
+
return {
|
|
1909
|
+
receipt: safeWorkspacePlanReceipt(publicPlan, files.map((file) => file.relPath), publicPlanDigest),
|
|
1910
|
+
spawn: {
|
|
1911
|
+
env: { ...resolvedPlan.env },
|
|
1912
|
+
flags: [...resolvedPlan.flags],
|
|
1913
|
+
...promptIntentFields(resolvedPlan, samePromptIntentValue),
|
|
1914
|
+
...systemPromptBindingField(resolvedPlan),
|
|
1915
|
+
},
|
|
1916
|
+
runtimeOnlyFiles: privateRuntimeFiles,
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1897
1919
|
// A path this package invented can only hold this package's own earlier
|
|
1898
1920
|
// output, so re-applying into the same workspace replaces it rather than
|
|
1899
1921
|
// failing the turn. `reject` still refuses: that policy is "nothing may
|
|
@@ -1906,7 +1928,7 @@ function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDir, opti
|
|
|
1906
1928
|
if (workspaceFileExists(file.absPath) &&
|
|
1907
1929
|
!mayReplace(file) &&
|
|
1908
1930
|
(existingFiles === "reject" || !workspaceFileMatches(file))) {
|
|
1909
|
-
throw new Error(
|
|
1931
|
+
throw new Error(workspaceFileConflictMessage(file, existingFiles, publicPlanDigest));
|
|
1910
1932
|
}
|
|
1911
1933
|
}
|
|
1912
1934
|
// Parent directories remain on failure because they may contain concurrent
|
|
@@ -1956,7 +1978,7 @@ function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDir, opti
|
|
|
1956
1978
|
continue;
|
|
1957
1979
|
}
|
|
1958
1980
|
if (isFsError(error, "EEXIST")) {
|
|
1959
|
-
throw new Error(
|
|
1981
|
+
throw new Error(workspaceFileConflictMessage(entry.file, existingFiles, publicPlanDigest), { cause: error });
|
|
1960
1982
|
}
|
|
1961
1983
|
throw error;
|
|
1962
1984
|
}
|
|
@@ -2033,6 +2055,42 @@ function workspaceFileMatches(file) {
|
|
|
2033
2055
|
(stats.mode & 0o777) === file.mode &&
|
|
2034
2056
|
readFileSync(file.absPath, "utf8") === file.content);
|
|
2035
2057
|
}
|
|
2058
|
+
function sha256Hex(content) {
|
|
2059
|
+
return createHash("sha256").update(content).digest("hex");
|
|
2060
|
+
}
|
|
2061
|
+
/**
|
|
2062
|
+
* The refusal names what differs — planned versus existing content digest,
|
|
2063
|
+
* mode, or file kind — plus the plan digest, so the operator can tell a
|
|
2064
|
+
* mid-session profile swap from an agent-edited workspace file without
|
|
2065
|
+
* reproducing the workspace. Reads here are best-effort diagnosis inside an
|
|
2066
|
+
* already-failing path; an unreadable existing file is reported, not thrown
|
|
2067
|
+
* over.
|
|
2068
|
+
*/
|
|
2069
|
+
function workspaceFileConflictMessage(file, existingFiles, planDigest) {
|
|
2070
|
+
const prefix = `Refusing to replace existing workspace file: ${file.relPath}`;
|
|
2071
|
+
if (existingFiles === "reject") {
|
|
2072
|
+
return `${prefix} — the reject policy forbids any pre-existing plan path (plan ${planDigest})`;
|
|
2073
|
+
}
|
|
2074
|
+
const planned = `planned sha256:${sha256Hex(file.content)} mode ${file.mode.toString(8)}`;
|
|
2075
|
+
let existing;
|
|
2076
|
+
try {
|
|
2077
|
+
const stats = lstatSync(file.absPath);
|
|
2078
|
+
if (!stats.isFile()) {
|
|
2079
|
+
existing = stats.isSymbolicLink()
|
|
2080
|
+
? "existing path is a symlink, not a regular file"
|
|
2081
|
+
: "existing path is not a regular file";
|
|
2082
|
+
}
|
|
2083
|
+
else {
|
|
2084
|
+
const mode = (stats.mode & 0o777).toString(8);
|
|
2085
|
+
existing = `existing sha256:${sha256Hex(readFileSync(file.absPath))} mode ${mode}`;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
catch (error) {
|
|
2089
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2090
|
+
existing = `existing file unreadable: ${message}`;
|
|
2091
|
+
}
|
|
2092
|
+
return `${prefix} — ${planned} != ${existing} (plan ${planDigest})`;
|
|
2093
|
+
}
|
|
2036
2094
|
function prepareWorkspaceFile(workspaceRoot, file) {
|
|
2037
2095
|
assertNoSymlinkPathComponents(workspaceRoot, file.absPath);
|
|
2038
2096
|
const temporaryPath = join(dirname(file.absPath), `.agent-profile-${randomUUID()}.tmp`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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": "0.
|
|
23
|
+
"@tangle-network/agent-interface": "0.53.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": ">=0.47.0 <0.
|
|
29
|
+
"@tangle-network/agent-interface": ">=0.47.0 <0.54.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsc -p tsconfig.json",
|