agentwheel 0.20.8 → 0.20.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/README.md +31 -0
- package/dist/{apply-OEKVIYBV.js → apply-JBULQGW5.js} +2 -2
- package/dist/{chunk-4AM3LNUQ.js → chunk-E7Z2YB2C.js} +118 -6
- package/dist/{chunk-JXX6EDIP.js → chunk-SHPNLNBR.js} +550 -309
- package/dist/index.js +965 -239
- package/dist/{transaction-D7VRZZX5.js → transaction-EPP7GHXD.js} +1 -1
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +14 -1
- package/skills/agentwheel-discovery/SKILL.md +1 -1
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
} from "./chunk-7VPI5J5Y.js";
|
|
8
8
|
|
|
9
9
|
// src/install/transaction.ts
|
|
10
|
-
import { createHash as
|
|
11
|
-
import { cp, mkdir as mkdir3, readFile as
|
|
12
|
-
import { dirname as dirname5, join as
|
|
10
|
+
import { createHash as createHash6 } from "crypto";
|
|
11
|
+
import { cp, mkdir as mkdir3, readFile as readFile8, rm as rm4, stat as stat2 } from "fs/promises";
|
|
12
|
+
import { dirname as dirname5, join as join7, resolve as resolve10 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/transport/local.ts
|
|
15
15
|
import { execFile } from "child_process";
|
|
@@ -151,13 +151,13 @@ async function spawnWithInput(command, args, input) {
|
|
|
151
151
|
if (result.stderr) throw new Error(result.stderr);
|
|
152
152
|
}
|
|
153
153
|
function waitForProcess(child, label) {
|
|
154
|
-
return new Promise((
|
|
154
|
+
return new Promise((resolve11, reject) => {
|
|
155
155
|
const stderr = [];
|
|
156
156
|
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
|
157
157
|
child.on("error", reject);
|
|
158
158
|
child.on("close", (code) => {
|
|
159
159
|
const message = Buffer.concat(stderr).toString("utf8");
|
|
160
|
-
if (code === 0)
|
|
160
|
+
if (code === 0) resolve11({ stderr: "" });
|
|
161
161
|
else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
|
|
162
162
|
});
|
|
163
163
|
});
|
|
@@ -488,8 +488,8 @@ function sanitizeStateKey(value) {
|
|
|
488
488
|
|
|
489
489
|
// src/mutation/coordinator.ts
|
|
490
490
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
491
|
-
import { homedir as
|
|
492
|
-
import { resolve as
|
|
491
|
+
import { homedir as homedir5 } from "os";
|
|
492
|
+
import { isAbsolute as isAbsolute6, relative as relative3, resolve as resolve9, sep as sep3 } from "path";
|
|
493
493
|
|
|
494
494
|
// src/model/mutation.ts
|
|
495
495
|
import { z as z3 } from "zod";
|
|
@@ -548,10 +548,10 @@ var mutationPolicySchema = z3.object({
|
|
|
548
548
|
});
|
|
549
549
|
|
|
550
550
|
// src/model/workspace.ts
|
|
551
|
-
import { readFile as
|
|
552
|
-
import { homedir as
|
|
553
|
-
import { dirname as dirname4, join as
|
|
554
|
-
import { z as
|
|
551
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
552
|
+
import { homedir as homedir4 } from "os";
|
|
553
|
+
import { dirname as dirname4, join as join5, resolve as resolve6 } from "path";
|
|
554
|
+
import { z as z7 } from "zod";
|
|
555
555
|
|
|
556
556
|
// src/resolve/semver.ts
|
|
557
557
|
var semverPattern = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -651,7 +651,7 @@ function compareSemver(a, b) {
|
|
|
651
651
|
|
|
652
652
|
// src/mutation/declarations.ts
|
|
653
653
|
import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, writeSync } from "fs";
|
|
654
|
-
import { isAbsolute as
|
|
654
|
+
import { isAbsolute as isAbsolute4, join as join4, relative, resolve as resolve5, sep } from "path";
|
|
655
655
|
|
|
656
656
|
// src/mutation/protocol.ts
|
|
657
657
|
import { createHash } from "crypto";
|
|
@@ -808,16 +808,280 @@ function canonicalJson(value) {
|
|
|
808
808
|
}
|
|
809
809
|
|
|
810
810
|
// src/mutation/receipts.ts
|
|
811
|
-
import { createHash as
|
|
812
|
-
import { chmod, link, mkdir as mkdir2, open, readFile as
|
|
811
|
+
import { createHash as createHash3, randomUUID } from "crypto";
|
|
812
|
+
import { chmod, link, mkdir as mkdir2, open, readFile as readFile4, readdir as readdir2, rename as rename2, rm as rm2 } from "fs/promises";
|
|
813
|
+
import { homedir as homedir3 } from "os";
|
|
814
|
+
import { dirname as dirname3, join as join3, resolve as resolve4 } from "path";
|
|
815
|
+
import { z as z6 } from "zod";
|
|
816
|
+
|
|
817
|
+
// src/mutation/session-ownership.ts
|
|
818
|
+
import { createHash as createHash2 } from "crypto";
|
|
819
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
813
820
|
import { homedir as homedir2 } from "os";
|
|
814
|
-
import {
|
|
821
|
+
import { isAbsolute as isAbsolute3, join as join2, posix as posix2, resolve as resolve3 } from "path";
|
|
815
822
|
import { z as z5 } from "zod";
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
823
|
+
|
|
824
|
+
// src/mutation/process.ts
|
|
825
|
+
import { spawn as spawn2 } from "child_process";
|
|
826
|
+
async function runProcess(command, args, options = {}) {
|
|
827
|
+
return new Promise((resolve11, reject) => {
|
|
828
|
+
const useProcessGroup = options.killProcessGroup === true && process.platform !== "win32";
|
|
829
|
+
const child = spawn2(command, args, {
|
|
830
|
+
cwd: options.cwd,
|
|
831
|
+
env: options.env,
|
|
832
|
+
stdio: options.inheritedFileDescriptor === void 0 ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe", options.inheritedFileDescriptor],
|
|
833
|
+
detached: useProcessGroup,
|
|
834
|
+
windowsHide: true
|
|
835
|
+
});
|
|
836
|
+
const stdout = [];
|
|
837
|
+
const stderr = [];
|
|
838
|
+
let stdoutBytes = 0;
|
|
839
|
+
let stderrBytes = 0;
|
|
840
|
+
let outputExceeded = false;
|
|
841
|
+
let timedOut = false;
|
|
842
|
+
const terminate = () => {
|
|
843
|
+
if (useProcessGroup && child.pid !== void 0) {
|
|
844
|
+
try {
|
|
845
|
+
process.kill(-child.pid, "SIGKILL");
|
|
846
|
+
return;
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
child.kill("SIGKILL");
|
|
851
|
+
};
|
|
852
|
+
const timeout = options.timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
853
|
+
timedOut = true;
|
|
854
|
+
terminate();
|
|
855
|
+
}, options.timeoutMs);
|
|
856
|
+
const collect = (target, stream, chunk) => {
|
|
857
|
+
if (outputExceeded) return;
|
|
858
|
+
if (stream === "stdout") stdoutBytes += chunk.length;
|
|
859
|
+
else stderrBytes += chunk.length;
|
|
860
|
+
if (options.maxOutputBytes !== void 0 && (stdoutBytes > options.maxOutputBytes || stderrBytes > options.maxOutputBytes)) {
|
|
861
|
+
outputExceeded = true;
|
|
862
|
+
terminate();
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
target.push(chunk);
|
|
866
|
+
};
|
|
867
|
+
child.stdout.on("data", (chunk) => collect(stdout, "stdout", chunk));
|
|
868
|
+
child.stderr.on("data", (chunk) => collect(stderr, "stderr", chunk));
|
|
869
|
+
child.on("error", reject);
|
|
870
|
+
child.on("close", (code) => {
|
|
871
|
+
if (timeout) clearTimeout(timeout);
|
|
872
|
+
const exitCode = code ?? 1;
|
|
873
|
+
const result = { stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr), exitCode };
|
|
874
|
+
if (timedOut) {
|
|
875
|
+
reject(new Error(`${command} exceeded the ${options.timeoutMs}ms execution timeout.`));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (outputExceeded) {
|
|
879
|
+
reject(new Error(`${command} exceeded the provider output limit.`));
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (!(options.allowExitCodes ?? [0]).includes(exitCode)) {
|
|
883
|
+
const detail = options.includeFailureOutput === false ? "" : result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim();
|
|
884
|
+
reject(new Error(`${command} exited ${exitCode}${detail ? `: ${detail}` : ""}`));
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
resolve11(result);
|
|
888
|
+
});
|
|
889
|
+
if (options.input !== void 0) child.stdin.end(options.input);
|
|
890
|
+
else child.stdin.end();
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/mutation/session-ownership.ts
|
|
895
|
+
var safeSingleLineSchema = z5.string().max(4096).refine(
|
|
896
|
+
(value) => !/[\r\n\u0000-\u001f\u007f]/u.test(value),
|
|
897
|
+
{ message: "must be a single printable line" }
|
|
898
|
+
);
|
|
899
|
+
var opaqueRefSchema = z5.string().regex(/^[a-z][a-z0-9-]{0,63}:[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/);
|
|
900
|
+
var graphNodeStatusSchema = z5.enum(["active", "waiting", "blocked", "quiet", "closed"]);
|
|
901
|
+
var graphNodeSchema = z5.object({
|
|
902
|
+
id: safeSingleLineSchema.pipe(z5.string().min(1)),
|
|
903
|
+
agent: safeSingleLineSchema.pipe(z5.string().min(1)),
|
|
904
|
+
tmuxTarget: safeSingleLineSchema,
|
|
905
|
+
roleProfile: safeSingleLineSchema.optional().default(""),
|
|
906
|
+
title: safeSingleLineSchema.optional().default(""),
|
|
907
|
+
summary: safeSingleLineSchema.optional().default(""),
|
|
908
|
+
status: graphNodeStatusSchema,
|
|
909
|
+
domains: z5.array(safeSingleLineSchema).optional().default([]),
|
|
910
|
+
runtimeUuid: z5.string().uuid().nullable().optional().default(null),
|
|
911
|
+
refs: z5.array(opaqueRefSchema).optional().default([]),
|
|
912
|
+
createdAt: z5.string().datetime(),
|
|
913
|
+
lastSeenAt: z5.string().datetime().optional()
|
|
914
|
+
}).passthrough();
|
|
915
|
+
var sessionGraphSchema = z5.object({
|
|
916
|
+
schema: z5.literal("agent-mesh.session-graph.v1"),
|
|
917
|
+
generatedAt: z5.string().datetime(),
|
|
918
|
+
nodes: z5.array(graphNodeSchema)
|
|
919
|
+
}).passthrough();
|
|
920
|
+
var liveStatuses = /* @__PURE__ */ new Set(["active", "waiting", "blocked"]);
|
|
921
|
+
var runtimeUuidSchema = z5.string().uuid();
|
|
922
|
+
function agentMeshGraphPath() {
|
|
923
|
+
if (process.env.AGENT_MESH_GRAPH_PATH) return resolve3(process.env.AGENT_MESH_GRAPH_PATH);
|
|
924
|
+
const stateRoot = process.env.XDG_STATE_HOME ? resolve3(process.env.XDG_STATE_HOME) : join2(homedir2(), ".local", "state");
|
|
925
|
+
return join2(stateRoot, "agent-mesh", "graph", "graph.json");
|
|
926
|
+
}
|
|
927
|
+
function agentwheelResourceRef(gitCommonDir, repositoryRelativePath) {
|
|
928
|
+
const commonDir = resolve3(gitCommonDir);
|
|
929
|
+
const normalizedPath = normalizeRepositoryRelativePath(repositoryRelativePath);
|
|
930
|
+
const digest = createHash2("sha256").update(commonDir).update("\0").update(normalizedPath).digest("hex");
|
|
931
|
+
return `agentwheel-resource:${digest}`;
|
|
932
|
+
}
|
|
933
|
+
async function resourceRefsForRepositoryPaths(repositoryRoot, paths) {
|
|
934
|
+
const result = await runProcess("git", [
|
|
935
|
+
"-C",
|
|
936
|
+
repositoryRoot,
|
|
937
|
+
"rev-parse",
|
|
938
|
+
"--path-format=absolute",
|
|
939
|
+
"--git-common-dir"
|
|
940
|
+
]);
|
|
941
|
+
const gitCommonDir = result.stdout.toString("utf8").trim();
|
|
942
|
+
if (!gitCommonDir || !isAbsolute3(gitCommonDir)) {
|
|
943
|
+
throw new Error("Git did not return an absolute common directory for ownership diagnostics.");
|
|
944
|
+
}
|
|
945
|
+
return new Map([...paths].map((path) => [path, agentwheelResourceRef(gitCommonDir, path)]));
|
|
946
|
+
}
|
|
947
|
+
async function lookupResourceOwners(refs) {
|
|
948
|
+
const requested = [...new Set(refs)];
|
|
949
|
+
const graph = await readSessionGraph();
|
|
950
|
+
if (!graph.ok) {
|
|
951
|
+
return new Map(requested.map((ref) => [ref, { kind: "unknown", reason: graph.reason }]));
|
|
952
|
+
}
|
|
953
|
+
return new Map(requested.map((ref) => [ref, ownershipForNodes(
|
|
954
|
+
graph.nodes.filter((node) => node.refs.includes(ref))
|
|
955
|
+
)]));
|
|
956
|
+
}
|
|
957
|
+
async function lookupRuntimeOwner(runtimeUuid) {
|
|
958
|
+
const graph = await readSessionGraph();
|
|
959
|
+
if (!graph.ok) return { kind: "unknown", reason: graph.reason };
|
|
960
|
+
const normalized = runtimeUuid.toLowerCase();
|
|
961
|
+
return ownershipForNodes(graph.nodes.filter((node) => node.runtimeUuid?.toLowerCase() === normalized));
|
|
962
|
+
}
|
|
963
|
+
async function describeDirtyPathOwnershipByPath(repositoryRoot, paths) {
|
|
964
|
+
let refs;
|
|
965
|
+
try {
|
|
966
|
+
refs = await resourceRefsForRepositoryPaths(repositoryRoot, paths);
|
|
967
|
+
} catch {
|
|
968
|
+
return new Map(paths.map((path) => [path, `${path}: ${unknownNextAction("resource identity could not be computed")}`]));
|
|
969
|
+
}
|
|
970
|
+
const ownership = await lookupResourceOwners(refs.values());
|
|
971
|
+
return new Map(paths.map((path) => {
|
|
972
|
+
const match = ownership.get(refs.get(path));
|
|
973
|
+
if (match?.kind === "unique") {
|
|
974
|
+
return [path, `${path}: belongs to ${describeSession(match.owner)}. Wait for that session's handoff or ask the rollout coordinator before retrying.`];
|
|
975
|
+
}
|
|
976
|
+
if (match?.kind === "ambiguous") {
|
|
977
|
+
return [path, `${path}: owner unknown because multiple live sessions claim this resource. Candidates: ${match.candidates.map(describeSession).join("; ")}. Ask the rollout coordinator to disambiguate the candidates before retrying; do not remove the safety block.`];
|
|
978
|
+
}
|
|
979
|
+
return [path, `${path}: ${unknownNextAction(reasonText(match?.kind === "unknown" ? match.reason : "no-live-match"))}`];
|
|
980
|
+
}));
|
|
981
|
+
}
|
|
982
|
+
async function describeMutationLockOwner(owner) {
|
|
983
|
+
if (!owner) {
|
|
984
|
+
return unknownNextAction("the lock owner metadata is missing or malformed");
|
|
985
|
+
}
|
|
986
|
+
const facts = [
|
|
987
|
+
owner.operationId ? `operation ${owner.operationId}` : void 0,
|
|
988
|
+
owner.pid !== void 0 ? `pid ${owner.pid}` : void 0,
|
|
989
|
+
owner.runtimeUuid ? `runtime UUID ${owner.runtimeUuid}` : void 0,
|
|
990
|
+
owner.createdAt ? `lock created at ${owner.createdAt}` : void 0
|
|
991
|
+
].filter(Boolean).join(", ");
|
|
992
|
+
if (!owner.runtimeUuid) {
|
|
993
|
+
return `Lock owner facts: ${facts || "none"}. ${unknownNextAction("no runtime UUID was recorded")}`;
|
|
994
|
+
}
|
|
995
|
+
const match = await lookupRuntimeOwner(owner.runtimeUuid);
|
|
996
|
+
if (match.kind === "unique") {
|
|
997
|
+
return `Lock owner facts: ${facts}. Correlated to ${describeSession(match.owner)}. Wait for that session's handoff or ask the rollout coordinator before retrying.`;
|
|
998
|
+
}
|
|
999
|
+
if (match.kind === "ambiguous") {
|
|
1000
|
+
return `Lock owner facts: ${facts}. Owner unknown because the runtime UUID matches multiple live sessions. Candidates: ${match.candidates.map(describeSession).join("; ")}. Ask the rollout coordinator to disambiguate before retrying; do not remove the lock.`;
|
|
1001
|
+
}
|
|
1002
|
+
return `Lock owner facts: ${facts}. ${unknownNextAction(reasonText(match.reason))}`;
|
|
1003
|
+
}
|
|
1004
|
+
function runtimeUuidForCurrentProcess() {
|
|
1005
|
+
for (const name of ["AGENT_MESH_RUNTIME_UUID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "CLAUDE_CODE_SESSION_ID"]) {
|
|
1006
|
+
const value = process.env[name]?.trim();
|
|
1007
|
+
if (value && runtimeUuidSchema.safeParse(value).success) return value.toLowerCase();
|
|
1008
|
+
}
|
|
1009
|
+
return void 0;
|
|
1010
|
+
}
|
|
1011
|
+
function ownershipForNodes(nodes) {
|
|
1012
|
+
const live = nodes.filter((node) => liveStatuses.has(node.status)).map(sessionFacts);
|
|
1013
|
+
if (live.length === 1) return { kind: "unique", owner: live[0] };
|
|
1014
|
+
if (live.length > 1) return { kind: "ambiguous", candidates: live.sort((a, b) => a.sessionId.localeCompare(b.sessionId)) };
|
|
1015
|
+
if (nodes.length > 0) return { kind: "unknown", reason: "only-inactive-matches" };
|
|
1016
|
+
return { kind: "unknown", reason: "no-live-match" };
|
|
1017
|
+
}
|
|
1018
|
+
async function readSessionGraph() {
|
|
1019
|
+
let raw;
|
|
1020
|
+
try {
|
|
1021
|
+
raw = await readFile3(agentMeshGraphPath(), "utf8");
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
if (isMissing(error)) return { ok: false, reason: "missing-graph" };
|
|
1024
|
+
return { ok: false, reason: "malformed-graph" };
|
|
1025
|
+
}
|
|
1026
|
+
try {
|
|
1027
|
+
const graph = sessionGraphSchema.parse(JSON.parse(raw));
|
|
1028
|
+
return { ok: true, nodes: graph.nodes };
|
|
1029
|
+
} catch {
|
|
1030
|
+
return { ok: false, reason: "malformed-graph" };
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function sessionFacts(node) {
|
|
1034
|
+
return {
|
|
1035
|
+
nodeId: node.id,
|
|
1036
|
+
sessionId: node.tmuxTarget || node.runtimeUuid || node.id,
|
|
1037
|
+
agent: node.agent,
|
|
1038
|
+
runtimeUuid: node.runtimeUuid,
|
|
1039
|
+
status: node.status,
|
|
1040
|
+
domains: [...node.domains],
|
|
1041
|
+
createdAt: node.createdAt,
|
|
1042
|
+
summary: node.summary,
|
|
1043
|
+
title: node.title
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function describeSession(owner) {
|
|
1047
|
+
const context = owner.summary || owner.title || owner.domains.join(", ");
|
|
1048
|
+
return `session ${owner.sessionId}, ${owner.status} since ${owner.createdAt}${context ? ` on ${context}` : ""}`;
|
|
1049
|
+
}
|
|
1050
|
+
function unknownNextAction(reason) {
|
|
1051
|
+
return `Owner unknown because ${reason}. Inspect the Agent Mesh session graph or ask the rollout coordinator to identify or clear the owner before retrying; do not remove the safety block based on this diagnostic.`;
|
|
1052
|
+
}
|
|
1053
|
+
function reasonText(reason) {
|
|
1054
|
+
switch (reason) {
|
|
1055
|
+
case "missing-graph":
|
|
1056
|
+
return "the Agent Mesh graph projection is missing";
|
|
1057
|
+
case "malformed-graph":
|
|
1058
|
+
return "the Agent Mesh graph projection is malformed";
|
|
1059
|
+
case "only-inactive-matches":
|
|
1060
|
+
return "only quiet or closed session claims were found";
|
|
1061
|
+
case "no-live-match":
|
|
1062
|
+
return "no live session claim matches this resource";
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function normalizeRepositoryRelativePath(path) {
|
|
1066
|
+
if (!path || isAbsolute3(path) || path.includes("\\") || path.includes("\0")) {
|
|
1067
|
+
throw new Error("Ownership paths must be normalized repository-relative paths.");
|
|
1068
|
+
}
|
|
1069
|
+
const normalized = posix2.normalize(path);
|
|
1070
|
+
if (normalized !== path || path.split("/").some((part) => part === "" || part === "." || part === "..")) {
|
|
1071
|
+
throw new Error("Ownership paths must be normalized repository-relative paths.");
|
|
1072
|
+
}
|
|
1073
|
+
return normalized;
|
|
1074
|
+
}
|
|
1075
|
+
function isMissing(error) {
|
|
1076
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/mutation/receipts.ts
|
|
1080
|
+
var preexistingPathSchema = z6.object({
|
|
1081
|
+
path: z6.string().min(1).max(4096),
|
|
1082
|
+
sha256: z6.string().regex(/^[a-f0-9]{64}$/).nullable()
|
|
819
1083
|
}).strict();
|
|
820
|
-
var receiptStatusSchema =
|
|
1084
|
+
var receiptStatusSchema = z6.enum([
|
|
821
1085
|
"prepared",
|
|
822
1086
|
"handler-succeeded",
|
|
823
1087
|
"mutation-applied",
|
|
@@ -830,35 +1094,35 @@ var receiptStatusSchema = z5.enum([
|
|
|
830
1094
|
"postcheck-failed",
|
|
831
1095
|
"failed"
|
|
832
1096
|
]);
|
|
833
|
-
var runtimeJournalSchema =
|
|
834
|
-
path:
|
|
835
|
-
status:
|
|
836
|
-
transport:
|
|
837
|
-
transportDescription:
|
|
838
|
-
journalDigest:
|
|
1097
|
+
var runtimeJournalSchema = z6.object({
|
|
1098
|
+
path: z6.string().min(1).max(4096),
|
|
1099
|
+
status: z6.enum(["reserved", "pending", "resolving", "resolved"]),
|
|
1100
|
+
transport: z6.enum(["local", "ssh"]),
|
|
1101
|
+
transportDescription: z6.string().min(1).max(1024),
|
|
1102
|
+
journalDigest: z6.string().regex(/^[a-f0-9]{64}$/)
|
|
839
1103
|
}).strict();
|
|
840
|
-
var mutationReceiptBaseSchema =
|
|
841
|
-
version:
|
|
842
|
-
revision:
|
|
843
|
-
receiptDigest:
|
|
1104
|
+
var mutationReceiptBaseSchema = z6.object({
|
|
1105
|
+
version: z6.literal(1),
|
|
1106
|
+
revision: z6.number().int().min(1),
|
|
1107
|
+
receiptDigest: z6.string().regex(/^[a-f0-9]{64}$/),
|
|
844
1108
|
operationId: mutationOperationIdSchema,
|
|
845
|
-
commandName:
|
|
846
|
-
reason:
|
|
847
|
-
noCommit:
|
|
848
|
-
workspaceRoot:
|
|
849
|
-
repositoryRoot:
|
|
850
|
-
expectedHead:
|
|
851
|
-
expectedManifestDigest:
|
|
852
|
-
revisionMode:
|
|
1109
|
+
commandName: z6.string().min(1).max(256),
|
|
1110
|
+
reason: z6.string().min(1).max(4096),
|
|
1111
|
+
noCommit: z6.boolean(),
|
|
1112
|
+
workspaceRoot: z6.string().min(1),
|
|
1113
|
+
repositoryRoot: z6.string().min(1).nullable(),
|
|
1114
|
+
expectedHead: z6.string().min(1).max(256).nullable(),
|
|
1115
|
+
expectedManifestDigest: z6.string().regex(/^[a-f0-9]{64}$/).nullable(),
|
|
1116
|
+
revisionMode: z6.enum(["off", "commit-after-verify"]),
|
|
853
1117
|
provider: revisionProviderConfigSchema.nullable(),
|
|
854
|
-
preexistingPaths:
|
|
855
|
-
paths:
|
|
856
|
-
runtimeJournals:
|
|
1118
|
+
preexistingPaths: z6.array(preexistingPathSchema),
|
|
1119
|
+
paths: z6.array(revisionPathSchema),
|
|
1120
|
+
runtimeJournals: z6.array(runtimeJournalSchema).default([]),
|
|
857
1121
|
status: receiptStatusSchema,
|
|
858
|
-
createdAt:
|
|
859
|
-
updatedAt:
|
|
1122
|
+
createdAt: z6.string().datetime(),
|
|
1123
|
+
updatedAt: z6.string().datetime(),
|
|
860
1124
|
providerResponse: revisionProviderResultSchema.optional(),
|
|
861
|
-
error:
|
|
1125
|
+
error: z6.string().min(1).max(4096).optional()
|
|
862
1126
|
}).strict();
|
|
863
1127
|
var mutationReceiptSchema = mutationReceiptBaseSchema.superRefine((receipt, ctx) => {
|
|
864
1128
|
const expected = mutationReceiptDigest(receipt);
|
|
@@ -870,8 +1134,18 @@ var mutationReceiptSchema = mutationReceiptBaseSchema.superRefine((receipt, ctx)
|
|
|
870
1134
|
});
|
|
871
1135
|
}
|
|
872
1136
|
});
|
|
1137
|
+
var MutationLockContentionError = class extends Error {
|
|
1138
|
+
constructor(lockPath, owner, diagnostic) {
|
|
1139
|
+
super(`Another Agentwheel mutation owns the repository lock at ${lockPath}. ${diagnostic}`);
|
|
1140
|
+
this.lockPath = lockPath;
|
|
1141
|
+
this.owner = owner;
|
|
1142
|
+
this.name = "MutationLockContentionError";
|
|
1143
|
+
}
|
|
1144
|
+
lockPath;
|
|
1145
|
+
owner;
|
|
1146
|
+
};
|
|
873
1147
|
function mutationStateRoot() {
|
|
874
|
-
return
|
|
1148
|
+
return resolve4(process.env.AGENTWHEEL_MUTATION_STATE_ROOT ?? join3(homedir3(), ".agentwheel", "mutations"));
|
|
875
1149
|
}
|
|
876
1150
|
async function createMutationReceipt(receipt) {
|
|
877
1151
|
const path = receiptPath(receipt.operationId);
|
|
@@ -932,48 +1206,50 @@ async function readMutationReceipt(operationId) {
|
|
|
932
1206
|
const id = mutationOperationIdSchema.parse(operationId);
|
|
933
1207
|
const path = receiptPath(id);
|
|
934
1208
|
try {
|
|
935
|
-
return mutationReceiptSchema.parse(JSON.parse(await
|
|
1209
|
+
return mutationReceiptSchema.parse(JSON.parse(await readFile4(path, "utf8")));
|
|
936
1210
|
} catch (error) {
|
|
937
|
-
if (
|
|
1211
|
+
if (isMissing2(error)) throw new Error(`Unknown mutation operation '${id}'.`);
|
|
938
1212
|
throw error;
|
|
939
1213
|
}
|
|
940
1214
|
}
|
|
941
1215
|
async function listMutationReceipts() {
|
|
942
|
-
const root =
|
|
1216
|
+
const root = join3(mutationStateRoot(), "receipts");
|
|
943
1217
|
let names;
|
|
944
1218
|
try {
|
|
945
1219
|
names = await readdir2(root);
|
|
946
1220
|
} catch (error) {
|
|
947
|
-
if (
|
|
1221
|
+
if (isMissing2(error)) return [];
|
|
948
1222
|
throw error;
|
|
949
1223
|
}
|
|
950
1224
|
const receipts = [];
|
|
951
1225
|
for (const name of names.filter((entry) => entry.endsWith(".json")).sort()) {
|
|
952
|
-
receipts.push(mutationReceiptSchema.parse(JSON.parse(await
|
|
1226
|
+
receipts.push(mutationReceiptSchema.parse(JSON.parse(await readFile4(join3(root, name), "utf8"))));
|
|
953
1227
|
}
|
|
954
1228
|
return receipts.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
955
1229
|
}
|
|
956
1230
|
async function acquireMutationLock(repositoryRoot, operationId) {
|
|
957
1231
|
const root = mutationStateRoot();
|
|
958
|
-
const digest =
|
|
959
|
-
const lockPath =
|
|
960
|
-
await mkdir2(
|
|
961
|
-
await chmod(
|
|
1232
|
+
const digest = createHash3("sha256").update(resolve4(repositoryRoot)).digest("hex");
|
|
1233
|
+
const lockPath = join3(root, "locks", `${digest}.lock`);
|
|
1234
|
+
await mkdir2(join3(root, "locks"), { recursive: true, mode: 448 });
|
|
1235
|
+
await chmod(join3(root, "locks"), 448);
|
|
962
1236
|
try {
|
|
963
1237
|
await mkdir2(lockPath, { mode: 448 });
|
|
964
1238
|
} catch (error) {
|
|
965
1239
|
if (!isAlreadyExists(error)) throw error;
|
|
966
1240
|
if (!await reapStaleLock(lockPath)) {
|
|
967
|
-
|
|
1241
|
+
const owner = await readMutationLockOwner(lockPath);
|
|
1242
|
+
throw new MutationLockContentionError(lockPath, owner, await describeMutationLockOwner(owner));
|
|
968
1243
|
}
|
|
969
1244
|
await mkdir2(lockPath, { mode: 448 });
|
|
970
1245
|
}
|
|
971
|
-
const ownerPath =
|
|
1246
|
+
const ownerPath = join3(lockPath, "owner.json");
|
|
972
1247
|
await writeSecureJson(ownerPath, {
|
|
973
1248
|
version: 1,
|
|
974
1249
|
operationId,
|
|
975
1250
|
pid: process.pid,
|
|
976
|
-
|
|
1251
|
+
runtimeUuid: runtimeUuidForCurrentProcess(),
|
|
1252
|
+
repositoryRoot: resolve4(repositoryRoot),
|
|
977
1253
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
978
1254
|
});
|
|
979
1255
|
let released = false;
|
|
@@ -986,14 +1262,33 @@ async function acquireMutationLock(repositoryRoot, operationId) {
|
|
|
986
1262
|
}
|
|
987
1263
|
};
|
|
988
1264
|
}
|
|
1265
|
+
async function readMutationLockOwner(lockPath) {
|
|
1266
|
+
try {
|
|
1267
|
+
const parsed = JSON.parse(await readFile4(join3(lockPath, "owner.json"), "utf8"));
|
|
1268
|
+
const owner = {};
|
|
1269
|
+
if (typeof parsed.operationId === "string" && /^[a-z0-9][a-z0-9_-]{0,62}$/i.test(parsed.operationId)) {
|
|
1270
|
+
owner.operationId = parsed.operationId;
|
|
1271
|
+
}
|
|
1272
|
+
if (typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0) owner.pid = parsed.pid;
|
|
1273
|
+
if (typeof parsed.runtimeUuid === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(parsed.runtimeUuid)) {
|
|
1274
|
+
owner.runtimeUuid = parsed.runtimeUuid.toLowerCase();
|
|
1275
|
+
}
|
|
1276
|
+
if (typeof parsed.createdAt === "string" && !Number.isNaN(Date.parse(parsed.createdAt))) {
|
|
1277
|
+
owner.createdAt = parsed.createdAt;
|
|
1278
|
+
}
|
|
1279
|
+
return Object.keys(owner).length > 0 ? owner : void 0;
|
|
1280
|
+
} catch {
|
|
1281
|
+
return void 0;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
989
1284
|
function receiptPath(operationId) {
|
|
990
|
-
return
|
|
1285
|
+
return join3(mutationStateRoot(), "receipts", `${mutationOperationIdSchema.parse(operationId)}.json`);
|
|
991
1286
|
}
|
|
992
1287
|
async function ensureStateDirectory() {
|
|
993
1288
|
const root = mutationStateRoot();
|
|
994
|
-
await mkdir2(
|
|
1289
|
+
await mkdir2(join3(root, "receipts"), { recursive: true, mode: 448 });
|
|
995
1290
|
await chmod(root, 448);
|
|
996
|
-
await chmod(
|
|
1291
|
+
await chmod(join3(root, "receipts"), 448);
|
|
997
1292
|
}
|
|
998
1293
|
async function writeReceipt(path, receipt) {
|
|
999
1294
|
await writeSecureJson(path, receipt);
|
|
@@ -1004,7 +1299,7 @@ function sealMutationReceipt(value) {
|
|
|
1004
1299
|
}
|
|
1005
1300
|
function mutationReceiptDigest(receipt) {
|
|
1006
1301
|
const { receiptDigest: _ignored, ...payload } = receipt;
|
|
1007
|
-
return
|
|
1302
|
+
return createHash3("sha256").update(canonicalJson2(payload)).digest("hex");
|
|
1008
1303
|
}
|
|
1009
1304
|
function canonicalJson2(value) {
|
|
1010
1305
|
if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson2(entry === void 0 ? null : entry)).join(",")}]`;
|
|
@@ -1057,7 +1352,7 @@ async function acquireReceiptUpdateLock(path, operationId) {
|
|
|
1057
1352
|
}
|
|
1058
1353
|
async function reapStaleReceiptUpdateLock(lockPath) {
|
|
1059
1354
|
try {
|
|
1060
|
-
const owner = JSON.parse(await
|
|
1355
|
+
const owner = JSON.parse(await readFile4(lockPath, "utf8"));
|
|
1061
1356
|
if (typeof owner.pid !== "number" || processIsAlive(owner.pid)) return false;
|
|
1062
1357
|
} catch {
|
|
1063
1358
|
return false;
|
|
@@ -1096,16 +1391,16 @@ async function fsyncDirectory(path) {
|
|
|
1096
1391
|
}
|
|
1097
1392
|
async function fileExists(path) {
|
|
1098
1393
|
try {
|
|
1099
|
-
await
|
|
1394
|
+
await readFile4(path);
|
|
1100
1395
|
return true;
|
|
1101
1396
|
} catch (error) {
|
|
1102
|
-
if (
|
|
1397
|
+
if (isMissing2(error)) return false;
|
|
1103
1398
|
throw error;
|
|
1104
1399
|
}
|
|
1105
1400
|
}
|
|
1106
1401
|
async function reapStaleLock(lockPath) {
|
|
1107
1402
|
try {
|
|
1108
|
-
const owner = JSON.parse(await
|
|
1403
|
+
const owner = JSON.parse(await readFile4(join3(lockPath, "owner.json"), "utf8"));
|
|
1109
1404
|
if (typeof owner.pid !== "number" || processIsAlive(owner.pid)) return false;
|
|
1110
1405
|
} catch (error) {
|
|
1111
1406
|
return false;
|
|
@@ -1130,19 +1425,19 @@ function processIsAlive(pid) {
|
|
|
1130
1425
|
function isAlreadyExists(error) {
|
|
1131
1426
|
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
1132
1427
|
}
|
|
1133
|
-
function
|
|
1428
|
+
function isMissing2(error) {
|
|
1134
1429
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1135
1430
|
}
|
|
1136
1431
|
|
|
1137
1432
|
// src/mutation/declarations.ts
|
|
1138
1433
|
var active;
|
|
1139
|
-
function beginMutationPathDeclarations(repositoryRoot, operationIdInput, blockedPaths = []) {
|
|
1434
|
+
function beginMutationPathDeclarations(repositoryRoot, operationIdInput, blockedPaths = [], blockedPathDescriptions = /* @__PURE__ */ new Map()) {
|
|
1140
1435
|
if (active) throw new Error("A mutation path declaration scope is already active.");
|
|
1141
1436
|
const operationId = mutationOperationIdSchema.parse(operationIdInput);
|
|
1142
|
-
const root =
|
|
1437
|
+
const root = join4(mutationStateRoot(), "declarations");
|
|
1143
1438
|
mkdirSync(root, { recursive: true, mode: 448 });
|
|
1144
1439
|
chmodSync(root, 448);
|
|
1145
|
-
const journalPath =
|
|
1440
|
+
const journalPath = join4(root, `${operationId}.jsonl`);
|
|
1146
1441
|
const journal = openSync(journalPath, "wx", 384);
|
|
1147
1442
|
try {
|
|
1148
1443
|
fsyncSync(journal);
|
|
@@ -1156,38 +1451,41 @@ function beginMutationPathDeclarations(repositoryRoot, operationIdInput, blocked
|
|
|
1156
1451
|
closeSync(directory);
|
|
1157
1452
|
}
|
|
1158
1453
|
active = {
|
|
1159
|
-
repositoryRoot:
|
|
1454
|
+
repositoryRoot: resolve5(repositoryRoot),
|
|
1160
1455
|
operationId,
|
|
1161
1456
|
journalPath,
|
|
1162
1457
|
paths: /* @__PURE__ */ new Set(),
|
|
1163
|
-
blockedPaths: new Set(blockedPaths)
|
|
1458
|
+
blockedPaths: new Set(blockedPaths),
|
|
1459
|
+
blockedPathDescriptions
|
|
1164
1460
|
};
|
|
1165
1461
|
}
|
|
1166
1462
|
function resumeMutationPathDeclarations(repositoryRoot, operationIdInput, blockedPaths = []) {
|
|
1167
1463
|
if (active) throw new Error("A mutation path declaration scope is already active.");
|
|
1168
1464
|
const operationId = mutationOperationIdSchema.parse(operationIdInput);
|
|
1169
|
-
const journalPath =
|
|
1465
|
+
const journalPath = join4(mutationStateRoot(), "declarations", `${operationId}.jsonl`);
|
|
1170
1466
|
const paths = readMutationPathDeclarations(operationId);
|
|
1171
1467
|
if (paths.length === 0) {
|
|
1172
1468
|
const journal = openSync(journalPath, "r");
|
|
1173
1469
|
closeSync(journal);
|
|
1174
1470
|
}
|
|
1175
1471
|
active = {
|
|
1176
|
-
repositoryRoot:
|
|
1472
|
+
repositoryRoot: resolve5(repositoryRoot),
|
|
1177
1473
|
operationId,
|
|
1178
1474
|
journalPath,
|
|
1179
1475
|
paths: new Set(paths),
|
|
1180
|
-
blockedPaths: new Set(blockedPaths)
|
|
1476
|
+
blockedPaths: new Set(blockedPaths),
|
|
1477
|
+
blockedPathDescriptions: /* @__PURE__ */ new Map()
|
|
1181
1478
|
};
|
|
1182
1479
|
}
|
|
1183
1480
|
function declareMutationPath(path) {
|
|
1184
1481
|
if (!active) return;
|
|
1185
|
-
const absolute =
|
|
1482
|
+
const absolute = isAbsolute4(path) ? resolve5(path) : resolve5(process.cwd(), path);
|
|
1186
1483
|
const relativePath = relative(active.repositoryRoot, absolute);
|
|
1187
|
-
if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${sep}`) ||
|
|
1484
|
+
if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute4(relativePath)) return;
|
|
1188
1485
|
const normalized = relativePath.split(sep).join("/");
|
|
1189
1486
|
if (active.blockedPaths.has(normalized)) {
|
|
1190
|
-
|
|
1487
|
+
const description = active.blockedPathDescriptions.get(normalized);
|
|
1488
|
+
throw new Error(`Mutation intended path is already dirty and cannot be claimed: ${normalized}.${description ? ` ${description}` : ""}`);
|
|
1191
1489
|
}
|
|
1192
1490
|
if (active.paths.has(normalized)) return;
|
|
1193
1491
|
active.paths.add(normalized);
|
|
@@ -1208,7 +1506,7 @@ function endMutationPathDeclarations() {
|
|
|
1208
1506
|
}
|
|
1209
1507
|
function readMutationPathDeclarations(operationIdInput) {
|
|
1210
1508
|
const operationId = mutationOperationIdSchema.parse(operationIdInput);
|
|
1211
|
-
const path =
|
|
1509
|
+
const path = join4(mutationStateRoot(), "declarations", `${operationId}.jsonl`);
|
|
1212
1510
|
let content;
|
|
1213
1511
|
try {
|
|
1214
1512
|
content = readFileSync(path, "utf8");
|
|
@@ -1228,15 +1526,15 @@ function readMutationPathDeclarations(operationIdInput) {
|
|
|
1228
1526
|
}
|
|
1229
1527
|
|
|
1230
1528
|
// src/model/workspace.ts
|
|
1231
|
-
var artifactSelectorListSchema =
|
|
1529
|
+
var artifactSelectorListSchema = z7.array(z7.string().min(1));
|
|
1232
1530
|
var CURRENT_WORKSPACE_SCHEMA_VERSION = 4;
|
|
1233
|
-
var workspaceSelectionImportSchema =
|
|
1234
|
-
export:
|
|
1531
|
+
var workspaceSelectionImportSchema = z7.object({
|
|
1532
|
+
export: z7.string().min(1),
|
|
1235
1533
|
add: artifactSelectorListSchema.optional(),
|
|
1236
1534
|
exclude: artifactSelectorListSchema.optional()
|
|
1237
1535
|
}).strict();
|
|
1238
|
-
var workspaceSelectionExportSchema =
|
|
1239
|
-
extends:
|
|
1536
|
+
var workspaceSelectionExportSchema = z7.object({
|
|
1537
|
+
extends: z7.string().min(1).optional(),
|
|
1240
1538
|
select: artifactSelectorListSchema.optional(),
|
|
1241
1539
|
add: artifactSelectorListSchema.optional(),
|
|
1242
1540
|
exclude: artifactSelectorListSchema.optional()
|
|
@@ -1256,29 +1554,29 @@ var workspaceSelectionExportSchema = z6.object({
|
|
|
1256
1554
|
});
|
|
1257
1555
|
}
|
|
1258
1556
|
});
|
|
1259
|
-
var workspaceExportsSchema =
|
|
1260
|
-
selections:
|
|
1557
|
+
var workspaceExportsSchema = z7.object({
|
|
1558
|
+
selections: z7.record(z7.string().min(1), workspaceSelectionExportSchema).default({})
|
|
1261
1559
|
}).strict();
|
|
1262
|
-
var workspacePackageBaseSchema =
|
|
1263
|
-
name:
|
|
1264
|
-
source:
|
|
1265
|
-
driver:
|
|
1266
|
-
adapter:
|
|
1267
|
-
adapterConfig:
|
|
1268
|
-
adapterModule:
|
|
1269
|
-
adapterCodeHash:
|
|
1560
|
+
var workspacePackageBaseSchema = z7.object({
|
|
1561
|
+
name: z7.string().min(1),
|
|
1562
|
+
source: z7.string().min(1),
|
|
1563
|
+
driver: z7.enum(["local", "git", "skillkit", "vercel-skills", "mcp-registry", "clawhub"]).default("local"),
|
|
1564
|
+
adapter: z7.string().min(1).default("openclaw"),
|
|
1565
|
+
adapterConfig: z7.string().min(1).optional(),
|
|
1566
|
+
adapterModule: z7.string().min(1).optional(),
|
|
1567
|
+
adapterCodeHash: z7.string().min(16).optional(),
|
|
1270
1568
|
installationType: installationTypeSchema.optional(),
|
|
1271
|
-
mode:
|
|
1272
|
-
version:
|
|
1569
|
+
mode: z7.enum(["pinned", "tracking"]).default("pinned"),
|
|
1570
|
+
version: z7.string().min(1).refine(isSupportedVersionRange, {
|
|
1273
1571
|
message: "Version policy must be an exact semver, ~range, ^range, comparator range, or *"
|
|
1274
1572
|
}).optional(),
|
|
1275
|
-
requestedRef:
|
|
1276
|
-
select:
|
|
1277
|
-
skills:
|
|
1278
|
-
withSuggestions:
|
|
1279
|
-
suggestions:
|
|
1280
|
-
aliases:
|
|
1281
|
-
overrides:
|
|
1573
|
+
requestedRef: z7.string().min(1).optional(),
|
|
1574
|
+
select: z7.array(z7.string().min(1)).optional(),
|
|
1575
|
+
skills: z7.array(z7.string().min(1)).optional(),
|
|
1576
|
+
withSuggestions: z7.boolean().optional(),
|
|
1577
|
+
suggestions: z7.array(z7.string().min(1)).optional(),
|
|
1578
|
+
aliases: z7.record(z7.string(), z7.string().min(1)).optional(),
|
|
1579
|
+
overrides: z7.array(z7.string().min(1)).optional()
|
|
1282
1580
|
});
|
|
1283
1581
|
var workspacePackageSchema = workspacePackageBaseSchema.extend({
|
|
1284
1582
|
selection: workspaceSelectionImportSchema.optional()
|
|
@@ -1292,43 +1590,43 @@ var workspacePackageSchema = workspacePackageBaseSchema.extend({
|
|
|
1292
1590
|
}
|
|
1293
1591
|
});
|
|
1294
1592
|
var workspacePackageV1Schema = workspacePackageBaseSchema.extend({
|
|
1295
|
-
selection:
|
|
1593
|
+
selection: z7.never().optional()
|
|
1296
1594
|
});
|
|
1297
|
-
var commandSchema =
|
|
1298
|
-
var commandListSchema =
|
|
1299
|
-
var installStateKeySchema =
|
|
1300
|
-
var fleetIdSchema =
|
|
1301
|
-
var registeredFleetSchema =
|
|
1302
|
-
root:
|
|
1303
|
-
requiredPackages:
|
|
1595
|
+
var commandSchema = z7.array(z7.string().min(1)).min(1);
|
|
1596
|
+
var commandListSchema = z7.array(commandSchema).min(1).optional();
|
|
1597
|
+
var installStateKeySchema = z7.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i);
|
|
1598
|
+
var fleetIdSchema = z7.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i);
|
|
1599
|
+
var registeredFleetSchema = z7.object({
|
|
1600
|
+
root: z7.string().min(1),
|
|
1601
|
+
requiredPackages: z7.array(z7.string().min(1)).min(1)
|
|
1304
1602
|
}).strict().superRefine((fleet, ctx) => {
|
|
1305
1603
|
if (new Set(fleet.requiredPackages).size !== fleet.requiredPackages.length) {
|
|
1306
1604
|
ctx.addIssue({ code: "custom", path: ["requiredPackages"], message: "Required fleet packages must be unique." });
|
|
1307
1605
|
}
|
|
1308
1606
|
});
|
|
1309
|
-
var workspaceProfileRuntimeSchema =
|
|
1310
|
-
agent:
|
|
1311
|
-
adapter:
|
|
1312
|
-
adapterConfig:
|
|
1313
|
-
adapterModule:
|
|
1607
|
+
var workspaceProfileRuntimeSchema = z7.object({
|
|
1608
|
+
agent: z7.string().min(1).optional(),
|
|
1609
|
+
adapter: z7.string().min(1).default("openclaw"),
|
|
1610
|
+
adapterConfig: z7.string().min(1).optional(),
|
|
1611
|
+
adapterModule: z7.string().min(1).optional(),
|
|
1314
1612
|
installationType: installationTypeSchema.optional(),
|
|
1315
1613
|
stateKey: installStateKeySchema.optional(),
|
|
1316
|
-
targetRoot:
|
|
1317
|
-
executePlugins:
|
|
1318
|
-
reloadRuntimes:
|
|
1614
|
+
targetRoot: z7.string().min(1).optional(),
|
|
1615
|
+
executePlugins: z7.boolean().optional(),
|
|
1616
|
+
reloadRuntimes: z7.boolean().optional(),
|
|
1319
1617
|
reloadCommands: commandListSchema
|
|
1320
1618
|
});
|
|
1321
|
-
var workspaceProfileMemberSchema =
|
|
1322
|
-
id:
|
|
1323
|
-
workspace:
|
|
1324
|
-
profile:
|
|
1619
|
+
var workspaceProfileMemberSchema = z7.object({
|
|
1620
|
+
id: z7.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i),
|
|
1621
|
+
workspace: z7.string().min(1),
|
|
1622
|
+
profile: z7.string().min(1),
|
|
1325
1623
|
fleet: fleetIdSchema.optional(),
|
|
1326
|
-
transport:
|
|
1327
|
-
host:
|
|
1328
|
-
user:
|
|
1329
|
-
port:
|
|
1330
|
-
identityFile:
|
|
1331
|
-
refreshTtlSeconds:
|
|
1624
|
+
transport: z7.enum(["local", "ssh"]).default("local"),
|
|
1625
|
+
host: z7.string().min(1).optional(),
|
|
1626
|
+
user: z7.string().min(1).optional(),
|
|
1627
|
+
port: z7.number().int().positive().optional(),
|
|
1628
|
+
identityFile: z7.string().min(1).optional(),
|
|
1629
|
+
refreshTtlSeconds: z7.number().int().positive().optional()
|
|
1332
1630
|
}).strict().superRefine((member, ctx) => {
|
|
1333
1631
|
if (member.transport === "ssh" && !member.host) {
|
|
1334
1632
|
ctx.addIssue({ code: "custom", path: ["host"], message: "SSH profile members require host" });
|
|
@@ -1337,14 +1635,14 @@ var workspaceProfileMemberSchema = z6.object({
|
|
|
1337
1635
|
ctx.addIssue({ code: "custom", path: ["workspace"], message: "SSH profile member workspaces must be absolute" });
|
|
1338
1636
|
}
|
|
1339
1637
|
});
|
|
1340
|
-
var workspaceLeafProfileSchema =
|
|
1341
|
-
runtimes:
|
|
1342
|
-
members:
|
|
1638
|
+
var workspaceLeafProfileSchema = z7.object({
|
|
1639
|
+
runtimes: z7.array(workspaceProfileRuntimeSchema).min(1),
|
|
1640
|
+
members: z7.never().optional()
|
|
1343
1641
|
}).strict();
|
|
1344
|
-
var workspaceCompositeProfileSchema =
|
|
1345
|
-
members:
|
|
1346
|
-
runtimes:
|
|
1347
|
-
refreshTtlSeconds:
|
|
1642
|
+
var workspaceCompositeProfileSchema = z7.object({
|
|
1643
|
+
members: z7.array(workspaceProfileMemberSchema).min(1),
|
|
1644
|
+
runtimes: z7.never().optional(),
|
|
1645
|
+
refreshTtlSeconds: z7.number().int().positive().default(86400)
|
|
1348
1646
|
}).strict().superRefine((profile, ctx) => {
|
|
1349
1647
|
const seen = /* @__PURE__ */ new Set();
|
|
1350
1648
|
for (const [index, member] of profile.members.entries()) {
|
|
@@ -1354,32 +1652,32 @@ var workspaceCompositeProfileSchema = z6.object({
|
|
|
1354
1652
|
seen.add(member.id);
|
|
1355
1653
|
}
|
|
1356
1654
|
});
|
|
1357
|
-
var workspaceProfileSchema =
|
|
1655
|
+
var workspaceProfileSchema = z7.union([
|
|
1358
1656
|
workspaceLeafProfileSchema,
|
|
1359
1657
|
workspaceCompositeProfileSchema
|
|
1360
1658
|
]);
|
|
1361
|
-
var workspaceRegistrySchema =
|
|
1362
|
-
sources:
|
|
1363
|
-
ttlSeconds:
|
|
1659
|
+
var workspaceRegistrySchema = z7.object({
|
|
1660
|
+
sources: z7.array(z7.string().min(1)).optional(),
|
|
1661
|
+
ttlSeconds: z7.number().int().positive().optional()
|
|
1364
1662
|
}).default({});
|
|
1365
|
-
var workspaceTrustSchema =
|
|
1366
|
-
allow:
|
|
1367
|
-
acceptedSources:
|
|
1368
|
-
denyArtifactTypes:
|
|
1369
|
-
requireReviewForTransitive:
|
|
1663
|
+
var workspaceTrustSchema = z7.object({
|
|
1664
|
+
allow: z7.array(z7.string().min(1)).optional(),
|
|
1665
|
+
acceptedSources: z7.array(z7.string().min(1)).optional(),
|
|
1666
|
+
denyArtifactTypes: z7.array(artifactTypeSchema).optional(),
|
|
1667
|
+
requireReviewForTransitive: z7.boolean().optional()
|
|
1370
1668
|
}).default({});
|
|
1371
|
-
var workspaceAgentSchema =
|
|
1372
|
-
adapter:
|
|
1373
|
-
adapterConfig:
|
|
1374
|
-
adapterModule:
|
|
1375
|
-
root:
|
|
1669
|
+
var workspaceAgentSchema = z7.object({
|
|
1670
|
+
adapter: z7.string().min(1),
|
|
1671
|
+
adapterConfig: z7.string().min(1).optional(),
|
|
1672
|
+
adapterModule: z7.string().min(1).optional(),
|
|
1673
|
+
root: z7.string().min(1),
|
|
1376
1674
|
installationType: installationTypeSchema.optional(),
|
|
1377
1675
|
stateKey: installStateKeySchema.optional(),
|
|
1378
|
-
transport:
|
|
1379
|
-
host:
|
|
1380
|
-
user:
|
|
1381
|
-
port:
|
|
1382
|
-
identityFile:
|
|
1676
|
+
transport: z7.enum(["local", "ssh"]).default("local"),
|
|
1677
|
+
host: z7.string().min(1).optional(),
|
|
1678
|
+
user: z7.string().min(1).optional(),
|
|
1679
|
+
port: z7.number().int().positive().optional(),
|
|
1680
|
+
identityFile: z7.string().min(1).optional(),
|
|
1383
1681
|
reloadCommands: commandListSchema
|
|
1384
1682
|
}).superRefine((agent, ctx) => {
|
|
1385
1683
|
if (agent.transport !== "ssh") return;
|
|
@@ -1391,39 +1689,39 @@ var workspaceAgentSchema = z6.object({
|
|
|
1391
1689
|
});
|
|
1392
1690
|
}
|
|
1393
1691
|
});
|
|
1394
|
-
var workspaceConfigBaseSchema =
|
|
1395
|
-
bootstrapSkills:
|
|
1692
|
+
var workspaceConfigBaseSchema = z7.object({
|
|
1693
|
+
bootstrapSkills: z7.boolean().optional(),
|
|
1396
1694
|
registry: workspaceRegistrySchema,
|
|
1397
1695
|
trust: workspaceTrustSchema,
|
|
1398
|
-
profiles:
|
|
1399
|
-
agents:
|
|
1696
|
+
profiles: z7.record(z7.string(), workspaceProfileSchema).default({}),
|
|
1697
|
+
agents: z7.record(z7.string(), workspaceAgentSchema).default({})
|
|
1400
1698
|
});
|
|
1401
1699
|
var workspaceConfigV1Schema = workspaceConfigBaseSchema.extend({
|
|
1402
|
-
schemaVersion:
|
|
1403
|
-
packages:
|
|
1404
|
-
exports:
|
|
1700
|
+
schemaVersion: z7.literal(1),
|
|
1701
|
+
packages: z7.array(workspacePackageV1Schema).default([]),
|
|
1702
|
+
exports: z7.never().optional()
|
|
1405
1703
|
}).strict();
|
|
1406
1704
|
var workspaceConfigV2Schema = workspaceConfigBaseSchema.extend({
|
|
1407
|
-
schemaVersion:
|
|
1408
|
-
packages:
|
|
1705
|
+
schemaVersion: z7.literal(2),
|
|
1706
|
+
packages: z7.array(workspacePackageSchema).default([]),
|
|
1409
1707
|
exports: workspaceExportsSchema.default({ selections: {} })
|
|
1410
1708
|
}).strict();
|
|
1411
1709
|
var workspaceConfigV3Schema = workspaceConfigBaseSchema.extend({
|
|
1412
|
-
schemaVersion:
|
|
1413
|
-
packages:
|
|
1710
|
+
schemaVersion: z7.literal(3),
|
|
1711
|
+
packages: z7.array(workspacePackageSchema).default([]),
|
|
1414
1712
|
exports: workspaceExportsSchema.default({ selections: {} }),
|
|
1415
1713
|
fleetId: fleetIdSchema.optional(),
|
|
1416
|
-
fleets:
|
|
1714
|
+
fleets: z7.record(fleetIdSchema, registeredFleetSchema).default({})
|
|
1417
1715
|
}).strict();
|
|
1418
1716
|
var workspaceConfigV4Schema = workspaceConfigBaseSchema.extend({
|
|
1419
|
-
schemaVersion:
|
|
1420
|
-
packages:
|
|
1717
|
+
schemaVersion: z7.literal(CURRENT_WORKSPACE_SCHEMA_VERSION),
|
|
1718
|
+
packages: z7.array(workspacePackageSchema).default([]),
|
|
1421
1719
|
exports: workspaceExportsSchema.default({ selections: {} }),
|
|
1422
1720
|
fleetId: fleetIdSchema.optional(),
|
|
1423
|
-
fleets:
|
|
1721
|
+
fleets: z7.record(fleetIdSchema, registeredFleetSchema).default({}),
|
|
1424
1722
|
mutationPolicy: mutationPolicySchema.optional()
|
|
1425
1723
|
}).strict();
|
|
1426
|
-
var workspaceConfigSchema =
|
|
1724
|
+
var workspaceConfigSchema = z7.discriminatedUnion("schemaVersion", [
|
|
1427
1725
|
workspaceConfigV1Schema,
|
|
1428
1726
|
workspaceConfigV2Schema,
|
|
1429
1727
|
workspaceConfigV3Schema,
|
|
@@ -1433,12 +1731,12 @@ function supportsFleetConfig(config) {
|
|
|
1433
1731
|
return config.schemaVersion === 3 || config.schemaVersion === 4;
|
|
1434
1732
|
}
|
|
1435
1733
|
function workspaceConfigPath(workspaceRoot) {
|
|
1436
|
-
return
|
|
1734
|
+
return join5(workspaceRoot, ".agentwheel", "config.json");
|
|
1437
1735
|
}
|
|
1438
1736
|
async function readWorkspaceConfig(workspaceRoot) {
|
|
1439
1737
|
const path = workspaceConfigPath(workspaceRoot);
|
|
1440
1738
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
1441
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
1739
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile5(path, "utf8")));
|
|
1442
1740
|
}
|
|
1443
1741
|
async function writeWorkspaceConfig(workspaceRoot, config) {
|
|
1444
1742
|
const path = workspaceConfigPath(workspaceRoot);
|
|
@@ -1452,20 +1750,20 @@ function upsertPackage(config, entry) {
|
|
|
1452
1750
|
packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
1453
1751
|
return workspaceConfigSchema.parse({ ...parsed, packages });
|
|
1454
1752
|
}
|
|
1455
|
-
function globalWorkspaceConfigPath(globalRoot =
|
|
1456
|
-
return
|
|
1753
|
+
function globalWorkspaceConfigPath(globalRoot = homedir4()) {
|
|
1754
|
+
return join5(globalRoot, ".agentwheel", "config.json");
|
|
1457
1755
|
}
|
|
1458
1756
|
async function findWorkspaceRoot(start = process.cwd()) {
|
|
1459
|
-
let current =
|
|
1757
|
+
let current = resolve6(start);
|
|
1460
1758
|
while (true) {
|
|
1461
1759
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
1462
1760
|
const parent = dirname4(current);
|
|
1463
|
-
if (parent === current) return
|
|
1761
|
+
if (parent === current) return resolve6(start);
|
|
1464
1762
|
current = parent;
|
|
1465
1763
|
}
|
|
1466
1764
|
}
|
|
1467
1765
|
async function findExistingWorkspaceRoot(start = process.cwd()) {
|
|
1468
|
-
let current =
|
|
1766
|
+
let current = resolve6(start);
|
|
1469
1767
|
while (true) {
|
|
1470
1768
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
1471
1769
|
const parent = dirname4(current);
|
|
@@ -1500,9 +1798,9 @@ function mergeWorkspaceConfig(global, project) {
|
|
|
1500
1798
|
});
|
|
1501
1799
|
}
|
|
1502
1800
|
function resolveConfigPath(path, baseRoot) {
|
|
1503
|
-
if (path.startsWith("~/")) return
|
|
1504
|
-
if (path === "~") return
|
|
1505
|
-
return path.startsWith("/") ?
|
|
1801
|
+
if (path.startsWith("~/")) return resolve6(homedir4(), path.slice(2));
|
|
1802
|
+
if (path === "~") return homedir4();
|
|
1803
|
+
return path.startsWith("/") ? resolve6(path) : resolve6(baseRoot, path);
|
|
1506
1804
|
}
|
|
1507
1805
|
function emptyWorkspaceConfig() {
|
|
1508
1806
|
return { schemaVersion: 1, packages: [], registry: {}, trust: {}, profiles: {}, agents: {} };
|
|
@@ -1512,7 +1810,7 @@ function isCompositeWorkspaceProfile(profile) {
|
|
|
1512
1810
|
}
|
|
1513
1811
|
async function readConfigPath(path) {
|
|
1514
1812
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
1515
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
1813
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile5(path, "utf8")));
|
|
1516
1814
|
}
|
|
1517
1815
|
function mergeWorkspaceTrust(global, project) {
|
|
1518
1816
|
return {
|
|
@@ -1527,87 +1825,15 @@ function sortedUnique(values) {
|
|
|
1527
1825
|
}
|
|
1528
1826
|
|
|
1529
1827
|
// src/mutation/providers.ts
|
|
1530
|
-
import { createHash as
|
|
1531
|
-
import { chmod as chmod2, mkdtemp, open as open2, readFile as
|
|
1828
|
+
import { createHash as createHash5 } from "crypto";
|
|
1829
|
+
import { chmod as chmod2, mkdtemp, open as open2, readFile as readFile7, rename as rename3, rm as rm3, rmdir, stat, writeFile as writeFile2 } from "fs/promises";
|
|
1532
1830
|
import { tmpdir } from "os";
|
|
1533
|
-
import { isAbsolute as
|
|
1534
|
-
|
|
1535
|
-
// src/mutation/repository.ts
|
|
1536
|
-
import { createHash as createHash3 } from "crypto";
|
|
1537
|
-
import { lstat, readFile as readFile5, readlink, readdir as readdir3 } from "fs/promises";
|
|
1538
|
-
import { posix as posix2, relative as relative2, resolve as resolve6, sep as sep2 } from "path";
|
|
1539
|
-
|
|
1540
|
-
// src/mutation/process.ts
|
|
1541
|
-
import { spawn as spawn2 } from "child_process";
|
|
1542
|
-
async function runProcess(command, args, options = {}) {
|
|
1543
|
-
return new Promise((resolve10, reject) => {
|
|
1544
|
-
const useProcessGroup = options.killProcessGroup === true && process.platform !== "win32";
|
|
1545
|
-
const child = spawn2(command, args, {
|
|
1546
|
-
cwd: options.cwd,
|
|
1547
|
-
env: options.env,
|
|
1548
|
-
stdio: options.inheritedFileDescriptor === void 0 ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe", options.inheritedFileDescriptor],
|
|
1549
|
-
detached: useProcessGroup,
|
|
1550
|
-
windowsHide: true
|
|
1551
|
-
});
|
|
1552
|
-
const stdout = [];
|
|
1553
|
-
const stderr = [];
|
|
1554
|
-
let stdoutBytes = 0;
|
|
1555
|
-
let stderrBytes = 0;
|
|
1556
|
-
let outputExceeded = false;
|
|
1557
|
-
let timedOut = false;
|
|
1558
|
-
const terminate = () => {
|
|
1559
|
-
if (useProcessGroup && child.pid !== void 0) {
|
|
1560
|
-
try {
|
|
1561
|
-
process.kill(-child.pid, "SIGKILL");
|
|
1562
|
-
return;
|
|
1563
|
-
} catch {
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
child.kill("SIGKILL");
|
|
1567
|
-
};
|
|
1568
|
-
const timeout = options.timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1569
|
-
timedOut = true;
|
|
1570
|
-
terminate();
|
|
1571
|
-
}, options.timeoutMs);
|
|
1572
|
-
const collect = (target, stream, chunk) => {
|
|
1573
|
-
if (outputExceeded) return;
|
|
1574
|
-
if (stream === "stdout") stdoutBytes += chunk.length;
|
|
1575
|
-
else stderrBytes += chunk.length;
|
|
1576
|
-
if (options.maxOutputBytes !== void 0 && (stdoutBytes > options.maxOutputBytes || stderrBytes > options.maxOutputBytes)) {
|
|
1577
|
-
outputExceeded = true;
|
|
1578
|
-
terminate();
|
|
1579
|
-
return;
|
|
1580
|
-
}
|
|
1581
|
-
target.push(chunk);
|
|
1582
|
-
};
|
|
1583
|
-
child.stdout.on("data", (chunk) => collect(stdout, "stdout", chunk));
|
|
1584
|
-
child.stderr.on("data", (chunk) => collect(stderr, "stderr", chunk));
|
|
1585
|
-
child.on("error", reject);
|
|
1586
|
-
child.on("close", (code) => {
|
|
1587
|
-
if (timeout) clearTimeout(timeout);
|
|
1588
|
-
const exitCode = code ?? 1;
|
|
1589
|
-
const result = { stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr), exitCode };
|
|
1590
|
-
if (timedOut) {
|
|
1591
|
-
reject(new Error(`${command} exceeded the ${options.timeoutMs}ms execution timeout.`));
|
|
1592
|
-
return;
|
|
1593
|
-
}
|
|
1594
|
-
if (outputExceeded) {
|
|
1595
|
-
reject(new Error(`${command} exceeded the provider output limit.`));
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
if (!(options.allowExitCodes ?? [0]).includes(exitCode)) {
|
|
1599
|
-
const detail = options.includeFailureOutput === false ? "" : result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim();
|
|
1600
|
-
reject(new Error(`${command} exited ${exitCode}${detail ? `: ${detail}` : ""}`));
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
resolve10(result);
|
|
1604
|
-
});
|
|
1605
|
-
if (options.input !== void 0) child.stdin.end(options.input);
|
|
1606
|
-
else child.stdin.end();
|
|
1607
|
-
});
|
|
1608
|
-
}
|
|
1831
|
+
import { isAbsolute as isAbsolute5, join as join6, resolve as resolve8 } from "path";
|
|
1609
1832
|
|
|
1610
1833
|
// src/mutation/repository.ts
|
|
1834
|
+
import { createHash as createHash4 } from "crypto";
|
|
1835
|
+
import { lstat, readFile as readFile6, readlink, readdir as readdir3 } from "fs/promises";
|
|
1836
|
+
import { posix as posix3, relative as relative2, resolve as resolve7, sep as sep2 } from "path";
|
|
1611
1837
|
async function discoverGitRepository(start) {
|
|
1612
1838
|
const probe = await runProcess("git", ["-C", start, "rev-parse", "--show-toplevel"], { allowExitCodes: [0, 128] });
|
|
1613
1839
|
if (probe.exitCode !== 0) return void 0;
|
|
@@ -1630,7 +1856,7 @@ async function assertGitPreflight(repository) {
|
|
|
1630
1856
|
if (result.exitCode === 0) throw new Error(`Revisioning refuses an in-progress Git operation (${marker}) in ${gitDir}.`);
|
|
1631
1857
|
}
|
|
1632
1858
|
for (const marker of ["rebase-merge", "rebase-apply"]) {
|
|
1633
|
-
if (await exists(
|
|
1859
|
+
if (await exists(resolve7(gitDir, marker))) throw new Error(`Revisioning refuses an in-progress Git operation (${marker}) in ${gitDir}.`);
|
|
1634
1860
|
}
|
|
1635
1861
|
}
|
|
1636
1862
|
async function snapshotRepository(repositoryRoot) {
|
|
@@ -1695,29 +1921,29 @@ async function hashHeadPath(repositoryRoot, head, path) {
|
|
|
1695
1921
|
return sha256(result.stdout);
|
|
1696
1922
|
}
|
|
1697
1923
|
async function hashWorkingPath(repositoryRoot, path) {
|
|
1698
|
-
const absolute =
|
|
1924
|
+
const absolute = resolve7(repositoryRoot, ...path.split("/"));
|
|
1699
1925
|
let stats;
|
|
1700
1926
|
try {
|
|
1701
1927
|
stats = await lstat(absolute);
|
|
1702
1928
|
} catch (error) {
|
|
1703
|
-
if (
|
|
1929
|
+
if (isMissing3(error)) return null;
|
|
1704
1930
|
throw error;
|
|
1705
1931
|
}
|
|
1706
1932
|
if (stats.isSymbolicLink()) return sha256(Buffer.from(await readlink(absolute), "utf8"));
|
|
1707
|
-
if (stats.isFile()) return sha256(await
|
|
1933
|
+
if (stats.isFile()) return sha256(await readFile6(absolute));
|
|
1708
1934
|
if (stats.isDirectory()) return hashDirectory(absolute);
|
|
1709
1935
|
throw new Error(`Unsupported repository path kind: ${path}`);
|
|
1710
1936
|
}
|
|
1711
1937
|
async function hashDirectory(root) {
|
|
1712
|
-
const hash =
|
|
1938
|
+
const hash = createHash4("sha256");
|
|
1713
1939
|
async function walk(dir) {
|
|
1714
1940
|
for (const entry of (await readdir3(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1715
1941
|
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
1716
|
-
const absolute =
|
|
1942
|
+
const absolute = resolve7(dir, entry.name);
|
|
1717
1943
|
const relativePath = normalizeRepoPath(relative2(root, absolute));
|
|
1718
1944
|
if (entry.isDirectory()) await walk(absolute);
|
|
1719
1945
|
else if (entry.isSymbolicLink()) hash.update(relativePath).update("\0").update(await readlink(absolute)).update("\0");
|
|
1720
|
-
else if (entry.isFile()) hash.update(relativePath).update("\0").update(await
|
|
1946
|
+
else if (entry.isFile()) hash.update(relativePath).update("\0").update(await readFile6(absolute)).update("\0");
|
|
1721
1947
|
}
|
|
1722
1948
|
}
|
|
1723
1949
|
await walk(root);
|
|
@@ -1727,12 +1953,12 @@ function splitNull(value) {
|
|
|
1727
1953
|
return value.toString("utf8").split("\0").filter(Boolean);
|
|
1728
1954
|
}
|
|
1729
1955
|
function normalizeRepoPath(path) {
|
|
1730
|
-
return sep2 === "/" ? path : path.split(sep2).join(
|
|
1956
|
+
return sep2 === "/" ? path : path.split(sep2).join(posix3.sep);
|
|
1731
1957
|
}
|
|
1732
1958
|
function sha256(value) {
|
|
1733
|
-
return
|
|
1959
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
1734
1960
|
}
|
|
1735
|
-
function
|
|
1961
|
+
function isMissing3(error) {
|
|
1736
1962
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1737
1963
|
}
|
|
1738
1964
|
async function exists(path) {
|
|
@@ -1740,7 +1966,7 @@ async function exists(path) {
|
|
|
1740
1966
|
await lstat(path);
|
|
1741
1967
|
return true;
|
|
1742
1968
|
} catch (error) {
|
|
1743
|
-
if (
|
|
1969
|
+
if (isMissing3(error)) return false;
|
|
1744
1970
|
throw error;
|
|
1745
1971
|
}
|
|
1746
1972
|
}
|
|
@@ -1845,7 +2071,7 @@ async function readVerifiedExecutable(executable, expectedSha256) {
|
|
|
1845
2071
|
const info = await handle.stat();
|
|
1846
2072
|
if (!info.isFile()) throw new Error(`Revision provider executable must be a regular file: ${executable}.`);
|
|
1847
2073
|
const bytes = await handle.readFile();
|
|
1848
|
-
const actualSha256 =
|
|
2074
|
+
const actualSha256 = createHash5("sha256").update(bytes).digest("hex");
|
|
1849
2075
|
if (actualSha256 !== expectedSha256) {
|
|
1850
2076
|
throw new Error(
|
|
1851
2077
|
`Revision provider executable hash mismatch for ${executable}: expected ${expectedSha256}, found ${actualSha256}.`
|
|
@@ -1862,8 +2088,8 @@ async function openImmutableExecutableSnapshot(bytes) {
|
|
|
1862
2088
|
`Command revision providers require an unlinked immutable executable snapshot, which is unsupported on ${process.platform}.`
|
|
1863
2089
|
);
|
|
1864
2090
|
}
|
|
1865
|
-
const root = await mkdtemp(
|
|
1866
|
-
const path =
|
|
2091
|
+
const root = await mkdtemp(join6(tmpdir(), "agentwheel-revision-command-"));
|
|
2092
|
+
const path = join6(root, "entrypoint");
|
|
1867
2093
|
let snapshot;
|
|
1868
2094
|
try {
|
|
1869
2095
|
await chmod2(root, 448);
|
|
@@ -1958,7 +2184,7 @@ async function assertProviderSemantics(request, response) {
|
|
|
1958
2184
|
}
|
|
1959
2185
|
async function invokeGitProvider(providerId, request) {
|
|
1960
2186
|
const repository = await discoverGitRepository(request.repositoryRoot);
|
|
1961
|
-
if (!repository || repository.root !==
|
|
2187
|
+
if (!repository || repository.root !== resolve8(request.repositoryRoot)) {
|
|
1962
2188
|
throw new Error(`Git revision provider requires the repository root itself: ${request.repositoryRoot}.`);
|
|
1963
2189
|
}
|
|
1964
2190
|
if (request.action === "check" || request.action === "preflight") {
|
|
@@ -2003,8 +2229,8 @@ async function finalizeGit(providerId, request) {
|
|
|
2003
2229
|
const indexLease = await captureIndexLease(request.repositoryRoot);
|
|
2004
2230
|
await assertIndexTree(request.repositoryRoot, request.expectedHead);
|
|
2005
2231
|
const hookBaseline = await snapshotRepository(request.repositoryRoot);
|
|
2006
|
-
const tempRoot = await mkdtemp(
|
|
2007
|
-
const indexPath =
|
|
2232
|
+
const tempRoot = await mkdtemp(join6(tmpdir(), "agentwheel-git-index-"));
|
|
2233
|
+
const indexPath = join6(tempRoot, "index");
|
|
2008
2234
|
const env = { ...process.env, GIT_INDEX_FILE: indexPath };
|
|
2009
2235
|
let commitSha;
|
|
2010
2236
|
try {
|
|
@@ -2015,14 +2241,14 @@ async function finalizeGit(providerId, request) {
|
|
|
2015
2241
|
await verifyTemporaryIndexPaths(request, env);
|
|
2016
2242
|
await runProcess("git", ["-C", request.repositoryRoot, "diff", "--cached", "--check"], { env });
|
|
2017
2243
|
const tree = (await runProcess("git", ["-C", request.repositoryRoot, "write-tree"], { env })).stdout.toString("utf8").trim();
|
|
2018
|
-
const messagePath =
|
|
2244
|
+
const messagePath = join6(tempRoot, "COMMIT_EDITMSG");
|
|
2019
2245
|
await writeFile2(messagePath, commitMessage(request, planDigest), { encoding: "utf8", mode: 384 });
|
|
2020
2246
|
await runCommitHooks(request, env, messagePath);
|
|
2021
2247
|
assertRepositorySnapshotEqual(hookBaseline, await snapshotRepository(request.repositoryRoot), "Git commit hooks");
|
|
2022
2248
|
const stagedAfterHooks = splitNull2((await runProcess("git", ["-C", request.repositoryRoot, "diff", "--cached", "--name-only", "-z"], { env })).stdout);
|
|
2023
2249
|
assertExactSet(stagedAfterHooks, request.paths.map((entry) => entry.path), "temporary Git index after hooks");
|
|
2024
2250
|
await verifyTemporaryIndexPaths(request, env);
|
|
2025
|
-
const message = await
|
|
2251
|
+
const message = await readFile7(messagePath, "utf8");
|
|
2026
2252
|
assertCommitMessageContract(message, request, planDigest);
|
|
2027
2253
|
commitSha = (await runProcess("git", ["-C", request.repositoryRoot, "commit-tree", tree, "-p", request.expectedHead], {
|
|
2028
2254
|
env,
|
|
@@ -2053,12 +2279,12 @@ async function runCommitHooks(request, env, messagePath) {
|
|
|
2053
2279
|
["commit-msg", [messagePath]]
|
|
2054
2280
|
]) {
|
|
2055
2281
|
const rawPath = (await runProcess("git", ["-C", request.repositoryRoot, "rev-parse", "--git-path", `hooks/${name}`])).stdout.toString("utf8").trim();
|
|
2056
|
-
const hookPath =
|
|
2282
|
+
const hookPath = isAbsolute5(rawPath) ? rawPath : resolve8(request.repositoryRoot, rawPath);
|
|
2057
2283
|
let executable = false;
|
|
2058
2284
|
try {
|
|
2059
2285
|
executable = ((await stat(hookPath)).mode & 73) !== 0;
|
|
2060
2286
|
} catch (error) {
|
|
2061
|
-
if (!
|
|
2287
|
+
if (!isMissing4(error)) throw error;
|
|
2062
2288
|
}
|
|
2063
2289
|
if (!executable) continue;
|
|
2064
2290
|
try {
|
|
@@ -2137,25 +2363,25 @@ async function realignIndexAfterRecoveredCommit(repositoryRoot, expectedHead, co
|
|
|
2137
2363
|
}
|
|
2138
2364
|
async function captureIndexLease(repositoryRoot) {
|
|
2139
2365
|
const rawPath = (await runProcess("git", ["-C", repositoryRoot, "rev-parse", "--git-path", "index"])).stdout.toString("utf8").trim();
|
|
2140
|
-
const path =
|
|
2366
|
+
const path = isAbsolute5(rawPath) ? rawPath : resolve8(repositoryRoot, rawPath);
|
|
2141
2367
|
try {
|
|
2142
|
-
const [content, info] = await Promise.all([
|
|
2368
|
+
const [content, info] = await Promise.all([readFile7(path), stat(path)]);
|
|
2143
2369
|
return { path, sha256: sha2562(content), mode: info.mode & 511 };
|
|
2144
2370
|
} catch (error) {
|
|
2145
|
-
if (
|
|
2371
|
+
if (isMissing4(error)) return { path, sha256: null, mode: 384 };
|
|
2146
2372
|
throw error;
|
|
2147
2373
|
}
|
|
2148
2374
|
}
|
|
2149
2375
|
async function replaceIndexCas(repositoryRoot, lease, commitSha) {
|
|
2150
|
-
const tempRoot = await mkdtemp(
|
|
2151
|
-
const replacementPath =
|
|
2376
|
+
const tempRoot = await mkdtemp(join6(tmpdir(), "agentwheel-index-replacement-"));
|
|
2377
|
+
const replacementPath = join6(tempRoot, "index");
|
|
2152
2378
|
const lockPath = `${lease.path}.lock`;
|
|
2153
2379
|
let handle;
|
|
2154
2380
|
try {
|
|
2155
2381
|
await runProcess("git", ["-C", repositoryRoot, "read-tree", commitSha], {
|
|
2156
2382
|
env: { ...process.env, GIT_INDEX_FILE: replacementPath }
|
|
2157
2383
|
});
|
|
2158
|
-
const replacement = await
|
|
2384
|
+
const replacement = await readFile7(replacementPath);
|
|
2159
2385
|
handle = await open2(lockPath, "wx", lease.mode);
|
|
2160
2386
|
const current = await readOptionalFile(lease.path);
|
|
2161
2387
|
const currentSha256 = current ? sha2562(current) : null;
|
|
@@ -2197,16 +2423,16 @@ async function hashGitObject(repositoryRoot, spec) {
|
|
|
2197
2423
|
}
|
|
2198
2424
|
async function readOptionalFile(path) {
|
|
2199
2425
|
try {
|
|
2200
|
-
return await
|
|
2426
|
+
return await readFile7(path);
|
|
2201
2427
|
} catch (error) {
|
|
2202
|
-
if (
|
|
2428
|
+
if (isMissing4(error)) return null;
|
|
2203
2429
|
throw error;
|
|
2204
2430
|
}
|
|
2205
2431
|
}
|
|
2206
2432
|
function sha2562(value) {
|
|
2207
|
-
return
|
|
2433
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
2208
2434
|
}
|
|
2209
|
-
function
|
|
2435
|
+
function isMissing4(error) {
|
|
2210
2436
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2211
2437
|
}
|
|
2212
2438
|
async function assertGitProviderCompatible(repositoryRoot) {
|
|
@@ -2352,15 +2578,18 @@ var GovernedMutation = class _GovernedMutation {
|
|
|
2352
2578
|
runtimeJournals: [],
|
|
2353
2579
|
status: "prepared"
|
|
2354
2580
|
});
|
|
2355
|
-
|
|
2581
|
+
const blockedPathDescriptions = baseline && repository && baseline.changed.size > 0 ? await describeDirtyPathOwnershipByPath(repository.root, [...baseline.changed.keys()]) : /* @__PURE__ */ new Map();
|
|
2582
|
+
const dirtyDeclaredPaths = baseline && repository ? dirtyRepositoryPaths(repository.root, baseline, options.anticipatedPaths ?? []) : [];
|
|
2583
|
+
if (dirtyDeclaredPaths.length > 0) {
|
|
2356
2584
|
throw new Error(
|
|
2357
|
-
`
|
|
2585
|
+
`Mutation declared paths are already dirty and cannot be claimed: ${dirtyDeclaredPaths.join(", ")}. ${dirtyDeclaredPaths.map((path) => blockedPathDescriptions.get(path)).join(" ")}`
|
|
2358
2586
|
);
|
|
2359
2587
|
}
|
|
2360
2588
|
beginMutationPathDeclarations(
|
|
2361
2589
|
repository?.root ?? options.workspaceRoot,
|
|
2362
2590
|
operationId,
|
|
2363
|
-
baseline?.changed.keys()
|
|
2591
|
+
baseline?.changed.keys(),
|
|
2592
|
+
blockedPathDescriptions
|
|
2364
2593
|
);
|
|
2365
2594
|
for (const path of options.anticipatedPaths ?? []) declareMutationPath(path);
|
|
2366
2595
|
if (policy.revisioning.mode === "commit-after-verify" && repository) {
|
|
@@ -2511,6 +2740,17 @@ var GovernedMutation = class _GovernedMutation {
|
|
|
2511
2740
|
await this.lock.release();
|
|
2512
2741
|
}
|
|
2513
2742
|
};
|
|
2743
|
+
function dirtyRepositoryPaths(repositoryRoot, baseline, paths) {
|
|
2744
|
+
const conflicts = /* @__PURE__ */ new Set();
|
|
2745
|
+
for (const path of paths) {
|
|
2746
|
+
const absolute = isAbsolute6(path) ? resolve9(path) : resolve9(process.cwd(), path);
|
|
2747
|
+
const relativePath = relative3(repositoryRoot, absolute);
|
|
2748
|
+
if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${sep3}`) || isAbsolute6(relativePath)) continue;
|
|
2749
|
+
const normalized = relativePath.split(sep3).join("/");
|
|
2750
|
+
if (baseline.changed.has(normalized)) conflicts.add(normalized);
|
|
2751
|
+
}
|
|
2752
|
+
return [...conflicts].sort((left, right) => left.localeCompare(right));
|
|
2753
|
+
}
|
|
2514
2754
|
var activeMutation;
|
|
2515
2755
|
function activeMutationMetadata() {
|
|
2516
2756
|
return activeMutation?.metadata();
|
|
@@ -2625,8 +2865,8 @@ async function recoverMutationRuntime(operationId, options = {}) {
|
|
|
2625
2865
|
}
|
|
2626
2866
|
mutation = GovernedMutation.activateExisting(receipt, baseline, lock);
|
|
2627
2867
|
const [{ recoverPendingApply }, { readLinkedLocalApplyJournal: readLinkedLocalApplyJournal2, removeApplyJournal: removeApplyJournal2, localPathExists: localPathExists2 }] = await Promise.all([
|
|
2628
|
-
import("./apply-
|
|
2629
|
-
import("./transaction-
|
|
2868
|
+
import("./apply-JBULQGW5.js"),
|
|
2869
|
+
import("./transaction-EPP7GHXD.js")
|
|
2630
2870
|
]);
|
|
2631
2871
|
let missingReservation = false;
|
|
2632
2872
|
for (const entry of pending) {
|
|
@@ -2714,10 +2954,10 @@ async function checkMutationProvider(workspaceRoot, globalRoot) {
|
|
|
2714
2954
|
});
|
|
2715
2955
|
return invokeRevisionProvider(policy.revisioning.provider, request, { workspaceRoot });
|
|
2716
2956
|
}
|
|
2717
|
-
async function mutationPolicyForWorkspace(workspaceRoot, globalRoot =
|
|
2957
|
+
async function mutationPolicyForWorkspace(workspaceRoot, globalRoot = homedir5()) {
|
|
2718
2958
|
const scopedConfig = await readWorkspaceConfig(workspaceRoot);
|
|
2719
2959
|
const scoped = scopedConfig.schemaVersion === 4 ? scopedConfig.mutationPolicy : void 0;
|
|
2720
|
-
if (
|
|
2960
|
+
if (resolve9(workspaceRoot) === resolve9(globalRoot)) return scoped;
|
|
2721
2961
|
const globalConfig = await readWorkspaceConfig(globalRoot);
|
|
2722
2962
|
const global = globalConfig.schemaVersion === 4 ? globalConfig.mutationPolicy : void 0;
|
|
2723
2963
|
return mergeMutationPolicies(global, scoped);
|
|
@@ -2790,17 +3030,17 @@ function upsertRuntimeJournal(entries, link2, status) {
|
|
|
2790
3030
|
// src/install/transaction.ts
|
|
2791
3031
|
function applyLockPath(targetRoot, adapter, scope = {}) {
|
|
2792
3032
|
const installationType = scope.installationType ?? "local";
|
|
2793
|
-
return
|
|
3033
|
+
return join7(metadataDir(targetRoot), `${stateKeyFor(adapter, { installationType })}.runtime-apply-lock`);
|
|
2794
3034
|
}
|
|
2795
3035
|
function applyJournalPath(targetRoot, adapter, scope = {}) {
|
|
2796
|
-
return
|
|
3036
|
+
return join7(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-journal.json`);
|
|
2797
3037
|
}
|
|
2798
3038
|
function applyBackupDir(targetRoot, adapter, scope = {}) {
|
|
2799
|
-
return
|
|
3039
|
+
return join7(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-backups`);
|
|
2800
3040
|
}
|
|
2801
3041
|
async function acquireApplyLock(targetRoot, adapter, transport = localTransport, options = {}, scope = {}) {
|
|
2802
3042
|
const lockPath = applyLockPath(targetRoot, adapter, scope);
|
|
2803
|
-
const ownerPath =
|
|
3043
|
+
const ownerPath = join7(lockPath, "owner.json");
|
|
2804
3044
|
const metadata = {
|
|
2805
3045
|
pid: process.pid,
|
|
2806
3046
|
adapter,
|
|
@@ -2864,7 +3104,7 @@ async function listApplyJournals(targetRoot, adapter, transport = localTransport
|
|
|
2864
3104
|
const found = [];
|
|
2865
3105
|
for (const fileName of await transport.listDir(metadataDir(targetRoot))) {
|
|
2866
3106
|
if (!fileName.endsWith(suffix)) continue;
|
|
2867
|
-
const path =
|
|
3107
|
+
const path = join7(metadataDir(targetRoot), fileName);
|
|
2868
3108
|
const journal = parseApplyJournal(JSON.parse(await transport.readFile(path)));
|
|
2869
3109
|
if (journal.adapter !== adapter || (journal.installationType ?? "local") !== installationType) continue;
|
|
2870
3110
|
found.push({ path, journal });
|
|
@@ -2876,15 +3116,15 @@ async function listAllApplyJournals(targetRoot, adapter, transport = localTransp
|
|
|
2876
3116
|
const found = [];
|
|
2877
3117
|
for (const fileName of await transport.listDir(metadataDir(targetRoot))) {
|
|
2878
3118
|
if (!fileName.endsWith(suffix)) continue;
|
|
2879
|
-
const path =
|
|
3119
|
+
const path = join7(metadataDir(targetRoot), fileName);
|
|
2880
3120
|
const journal = parseApplyJournal(JSON.parse(await transport.readFile(path)));
|
|
2881
3121
|
if (journal.adapter === adapter) found.push({ path, journal });
|
|
2882
3122
|
}
|
|
2883
3123
|
return found.sort((a, b) => a.path.localeCompare(b.path));
|
|
2884
3124
|
}
|
|
2885
3125
|
async function readLinkedLocalApplyJournal(path, operationId, expectedDigest, expectedTransportDescription) {
|
|
2886
|
-
const absolutePath =
|
|
2887
|
-
const journal = parseApplyJournal(JSON.parse(await
|
|
3126
|
+
const absolutePath = resolve10(path);
|
|
3127
|
+
const journal = parseApplyJournal(JSON.parse(await readFile8(absolutePath, "utf8")));
|
|
2888
3128
|
if (journal.version !== 2 || !journal.mutation) {
|
|
2889
3129
|
throw new Error(`Runtime apply journal ${absolutePath} is not linked to a governed mutation.`);
|
|
2890
3130
|
}
|
|
@@ -2902,7 +3142,7 @@ async function readLinkedLocalApplyJournal(path, operationId, expectedDigest, ex
|
|
|
2902
3142
|
}) !== expectedDigest) {
|
|
2903
3143
|
throw new Error(`Runtime apply journal ${absolutePath} digest does not match its durable receipt.`);
|
|
2904
3144
|
}
|
|
2905
|
-
const expectedPath =
|
|
3145
|
+
const expectedPath = resolve10(applyJournalPath(journal.targetRoot, journal.adapter, {
|
|
2906
3146
|
installationType: journal.installationType,
|
|
2907
3147
|
stateKey: journal.stateKey
|
|
2908
3148
|
}));
|
|
@@ -2926,7 +3166,7 @@ async function abortApplyJournal(targetRoot, adapter, transport = localTransport
|
|
|
2926
3166
|
const journalPath = applyJournalPath(targetRoot, adapter, scope);
|
|
2927
3167
|
if (!await transport.pathExists(journalPath)) return void 0;
|
|
2928
3168
|
const stateKey = stateKeyFor(adapter, scope);
|
|
2929
|
-
const archivePath =
|
|
3169
|
+
const archivePath = join7(metadataDir(targetRoot), "archive", `${stateKey}.apply-journal.failed-${journalTimestamp(/* @__PURE__ */ new Date())}.json`);
|
|
2930
3170
|
const content = await transport.readFile(journalPath);
|
|
2931
3171
|
const journal = parseApplyJournal(JSON.parse(content));
|
|
2932
3172
|
await transport.writeFileAtomic(archivePath, content.endsWith("\n") ? content : `${content}
|
|
@@ -2958,7 +3198,7 @@ function assertGovernedRuntimeTransportSupported(transport) {
|
|
|
2958
3198
|
}
|
|
2959
3199
|
function applyJournalLinkDigest(journal, transport) {
|
|
2960
3200
|
if (!journal.mutation) throw new Error("Cannot digest an unlinked runtime apply journal.");
|
|
2961
|
-
return
|
|
3201
|
+
return createHash6("sha256").update(canonicalJson3({
|
|
2962
3202
|
version: journal.version,
|
|
2963
3203
|
mutation: {
|
|
2964
3204
|
operationId: journal.mutation.operationId,
|
|
@@ -2970,11 +3210,12 @@ function applyJournalLinkDigest(journal, transport) {
|
|
|
2970
3210
|
adapter: journal.adapter,
|
|
2971
3211
|
installationType: journal.installationType ?? null,
|
|
2972
3212
|
stateKey: journal.stateKey ?? null,
|
|
2973
|
-
targetRoot:
|
|
3213
|
+
targetRoot: resolve10(journal.targetRoot),
|
|
2974
3214
|
baseRevision: journal.baseRevision,
|
|
2975
3215
|
graphLockDigest: journal.graphLockDigest ?? null,
|
|
2976
3216
|
graphLockPath: journal.graphLockPath ?? null,
|
|
2977
3217
|
graphLockRemovePath: journal.graphLockRemovePath ?? null,
|
|
3218
|
+
stateRemoveKey: journal.stateRemoveKey ?? null,
|
|
2978
3219
|
workspaceConfigPath: journal.workspaceConfigPath ?? null,
|
|
2979
3220
|
operations: journal.operations
|
|
2980
3221
|
})).digest("hex");
|
|
@@ -3041,7 +3282,7 @@ async function recordBackup(operation, index, targetRoot, adapter, transport = l
|
|
|
3041
3282
|
hadExisting
|
|
3042
3283
|
};
|
|
3043
3284
|
}
|
|
3044
|
-
const backupPath =
|
|
3285
|
+
const backupPath = join7(applyBackupDir(targetRoot, adapter, scope), String(index));
|
|
3045
3286
|
await rm4(backupPath, { recursive: true, force: true });
|
|
3046
3287
|
await mkdir3(dirname5(backupPath), { recursive: true });
|
|
3047
3288
|
await cp(operation.destPath, backupPath, { recursive: operation.kind === "dir", dereference: true });
|