agentwheel 0.20.19 → 0.21.0
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 +5 -0
- package/dist/index.js +8073 -1427
- package/dist/run-as-worker.js +150 -0
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +30 -1
- package/skills/agentwheel-discovery/SKILL.md +1 -1
- package/dist/apply-QJHTL7IK.js +0 -17
- package/dist/chunk-BHT57MK5.js +0 -3417
- package/dist/chunk-BNIF3FUR.js +0 -148
- package/dist/chunk-L37KQVRS.js +0 -2427
- package/dist/chunk-QABA4WUP.js +0 -34
- package/dist/identify-M6IA4RS7.js +0 -10
- package/dist/transaction-JHZJWBII.js +0 -42
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/transport/run-as-worker.ts
|
|
4
|
+
import { lstat, readdir as readdir2, readFile as readFile2, realpath } from "fs/promises";
|
|
5
|
+
import { isAbsolute, join as join2, parse, relative as relative2, resolve, sep } from "path";
|
|
6
|
+
|
|
7
|
+
// src/utils/fs.ts
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import {
|
|
10
|
+
copyFile,
|
|
11
|
+
cp,
|
|
12
|
+
mkdir,
|
|
13
|
+
readdir,
|
|
14
|
+
readFile,
|
|
15
|
+
rename,
|
|
16
|
+
rm,
|
|
17
|
+
stat,
|
|
18
|
+
writeFile
|
|
19
|
+
} from "fs/promises";
|
|
20
|
+
import { dirname, join, relative } from "path";
|
|
21
|
+
var IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "__pycache__", ".DS_Store"]);
|
|
22
|
+
var IGNORED_SUFFIXES = [".pyc", ".pyo"];
|
|
23
|
+
async function hashPath(path) {
|
|
24
|
+
const stats = await stat(path);
|
|
25
|
+
if (stats.isFile()) {
|
|
26
|
+
const content = await readFile(path);
|
|
27
|
+
return createHash("sha256").update("file\0").update(content).digest("hex");
|
|
28
|
+
}
|
|
29
|
+
if (!stats.isDirectory()) {
|
|
30
|
+
throw new Error(`Unsupported path kind: ${path}`);
|
|
31
|
+
}
|
|
32
|
+
const hash = createHash("sha256").update("dir\0");
|
|
33
|
+
const files = await listFiles(path);
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
hash.update(relative(path, file).replaceAll("\\", "/")).update("\0");
|
|
36
|
+
hash.update(await hashPath(file)).update("\0");
|
|
37
|
+
}
|
|
38
|
+
return hash.digest("hex");
|
|
39
|
+
}
|
|
40
|
+
async function listFiles(root) {
|
|
41
|
+
const out = [];
|
|
42
|
+
async function walk(dir) {
|
|
43
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
44
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
45
|
+
if (isIgnoredGeneratedEntry(entry.name)) continue;
|
|
46
|
+
const full = join(dir, entry.name);
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
await walk(full);
|
|
49
|
+
} else if (entry.isFile()) {
|
|
50
|
+
out.push(full);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
await walk(root);
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function isIgnoredGeneratedEntry(name) {
|
|
58
|
+
return IGNORED_ENTRY_NAMES.has(name) || IGNORED_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/transport/run-as-worker.ts
|
|
62
|
+
async function containedReadPath(root, requested) {
|
|
63
|
+
if (!isAbsolute(root) || resolve(root) !== root || !requested || requested.includes("\0") || requested.includes("\\") || isAbsolute(requested) || requested !== "." && requested.split(sep).some((part) => part === ".." || part === "." || part === "")) {
|
|
64
|
+
throw new Error("Run-as read path is not a contained relative path.");
|
|
65
|
+
}
|
|
66
|
+
let current = parse(root).root;
|
|
67
|
+
for (const part of root.slice(current.length).split(sep).filter(Boolean)) {
|
|
68
|
+
current = join2(current, part);
|
|
69
|
+
if ((await lstat(current)).isSymbolicLink()) throw new Error("Run-as root contains a symlink.");
|
|
70
|
+
}
|
|
71
|
+
if (await realpath(root) !== root) throw new Error("Run-as root changed.");
|
|
72
|
+
if (requested === ".") return root;
|
|
73
|
+
current = root;
|
|
74
|
+
for (const part of requested.split(sep)) {
|
|
75
|
+
current = join2(current, part);
|
|
76
|
+
try {
|
|
77
|
+
if ((await lstat(current)).isSymbolicLink()) throw new Error("Run-as path contains a symlink.");
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error.code === "ENOENT") break;
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const rel = relative2(root, current);
|
|
84
|
+
if (!rel || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw new Error("Run-as path escapes target root.");
|
|
85
|
+
return current;
|
|
86
|
+
}
|
|
87
|
+
async function rejectNestedLinks(path) {
|
|
88
|
+
const stat2 = await lstat(path);
|
|
89
|
+
if (stat2.isSymbolicLink()) throw new Error("Run-as read path contains a symlink.");
|
|
90
|
+
if (!stat2.isDirectory()) return;
|
|
91
|
+
for (const entry of await readdir2(path)) await rejectNestedLinks(join2(path, entry));
|
|
92
|
+
}
|
|
93
|
+
async function executeRead(root, request) {
|
|
94
|
+
if (request.version !== 1 || !["pathExists", "hashPath", "readFile", "listDir"].includes(request.operation)) {
|
|
95
|
+
throw new Error("Unsupported run-as read protocol request.");
|
|
96
|
+
}
|
|
97
|
+
const path = await containedReadPath(root, request.path);
|
|
98
|
+
let exists = true;
|
|
99
|
+
try {
|
|
100
|
+
await lstat(path);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error.code !== "ENOENT") throw error;
|
|
103
|
+
exists = false;
|
|
104
|
+
}
|
|
105
|
+
if (request.operation === "pathExists") return exists;
|
|
106
|
+
if (request.operation === "listDir" && !exists) return [];
|
|
107
|
+
if (!exists) throw new Error("Run-as target path does not exist.");
|
|
108
|
+
if (request.operation === "hashPath") {
|
|
109
|
+
await rejectNestedLinks(path);
|
|
110
|
+
return hashPath(path);
|
|
111
|
+
}
|
|
112
|
+
if (request.operation === "readFile") {
|
|
113
|
+
if (!(await lstat(path)).isFile()) throw new Error("Run-as readFile requires a regular file.");
|
|
114
|
+
return readFile2(path, "utf8");
|
|
115
|
+
}
|
|
116
|
+
return readdir2(path);
|
|
117
|
+
}
|
|
118
|
+
async function main() {
|
|
119
|
+
const [flagRoot, root, flagUid, rawUid, flagGid, rawGid] = process.argv.slice(2);
|
|
120
|
+
if (flagRoot !== "--root" || flagUid !== "--uid" || flagGid !== "--gid" || !root || !rawUid || !rawGid || process.argv.length !== 8) {
|
|
121
|
+
throw new Error("Invalid run-as worker arguments.");
|
|
122
|
+
}
|
|
123
|
+
const uid = Number(rawUid);
|
|
124
|
+
const gid = Number(rawGid);
|
|
125
|
+
if (!Number.isSafeInteger(uid) || uid <= 0 || !Number.isSafeInteger(gid) || gid < 0 || process.getuid?.() !== uid || process.getgid?.() !== gid || (await lstat(root)).uid !== uid) {
|
|
126
|
+
throw new Error("Run-as worker UID or root ownership mismatch.");
|
|
127
|
+
}
|
|
128
|
+
const chunks = [];
|
|
129
|
+
let size = 0;
|
|
130
|
+
for await (const chunk of process.stdin) {
|
|
131
|
+
size += chunk.length;
|
|
132
|
+
if (size > 65536) throw new Error("Run-as request exceeds 64 KiB.");
|
|
133
|
+
chunks.push(chunk);
|
|
134
|
+
}
|
|
135
|
+
const request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
136
|
+
const result = await executeRead(root, request);
|
|
137
|
+
process.stdout.write(`${JSON.stringify({ version: 1, result })}
|
|
138
|
+
`);
|
|
139
|
+
}
|
|
140
|
+
if (process.argv[1] && /run-as-worker\.(?:js|mjs|ts)$/.test(process.argv[1])) {
|
|
141
|
+
main().catch((error) => {
|
|
142
|
+
process.stderr.write(`run-as worker: ${error instanceof Error ? error.message : String(error)}
|
|
143
|
+
`);
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
containedReadPath,
|
|
149
|
+
executeRead
|
|
150
|
+
};
|
package/openpack.json
CHANGED
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
|
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
7
|
author: NestDevLab
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.21.0"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# agentwheel
|
|
@@ -357,6 +357,12 @@ Current config shape:
|
|
|
357
357
|
|
|
358
358
|
Use named targets:
|
|
359
359
|
|
|
360
|
+
Configured packages may declare `"runtimes": ["claude"]` to select adapter runtimes. All
|
|
361
|
+
configured-package plans honor it, including `--agent`, `--profile`, status, and update. Named
|
|
362
|
+
targets match their declared adapter; plain workspace commands match each package's adapter.
|
|
363
|
+
Agents sharing an adapter cannot be selected separately. Upgrade all members to a version
|
|
364
|
+
supporting this field before adding it to shared configuration.
|
|
365
|
+
|
|
360
366
|
```bash
|
|
361
367
|
agentwheel fleet register example-fleet --root /srv/agentwheel/fleets/example-fleet --required-package core-agent-pack
|
|
362
368
|
agentwheel fleet list
|
|
@@ -475,6 +481,9 @@ Drift means a managed runtime output changed outside agentwheel. Fix drift by ch
|
|
|
475
481
|
colliding upstream artifact.
|
|
476
482
|
- Eject an item into `.agentwheel/ejected` when the user wants local ownership.
|
|
477
483
|
|
|
484
|
+
When the plan instead keeps a file as foreign or refuses because another workspace owns it, that is
|
|
485
|
+
an ownership problem, not drift: see "Choosing an ownership command" under Troubleshooting.
|
|
486
|
+
|
|
478
487
|
Append durable local instruction text:
|
|
479
488
|
|
|
480
489
|
```bash
|
|
@@ -646,3 +655,23 @@ before Fleet qualification, owns paths under a fingerprint-only legacy key, run
|
|
|
646
655
|
--from-workspace-root <previous-owner-root> --json` from that workspace. It moves only proven,
|
|
647
656
|
still-desired entries into the stable state; add `--carry-drift` only after reviewing the drifted
|
|
648
657
|
paths. Apply the emitted command, then require a normal install plan without force flags.
|
|
658
|
+
|
|
659
|
+
### Choosing an ownership command
|
|
660
|
+
|
|
661
|
+
`agentwheel ownership --help` lists the commands; each subcommand's `--help` is the authoritative flag
|
|
662
|
+
reference. All of them dry-run by default and change only manifest metadata.
|
|
663
|
+
|
|
664
|
+
| Situation | Command |
|
|
665
|
+
|---|---|
|
|
666
|
+
| Unclear who owns what | `ownership recovery-plan` (read-only evidence report) |
|
|
667
|
+
| Refused: paths owned under a fingerprint-only legacy key | `ownership adopt-legacy` |
|
|
668
|
+
| `adopt-legacy` says entries are "already owned by the destination owner" (mixed legacy manifest) | Run a normal `install --agent <agent>` first; it migrates the state and keeps the foreign entry. Then hand off that entry. |
|
|
669
|
+
| Plan keeps one file as a `foreign artifact owned by …` in a stable manifest | `ownership handoff <type/name>` with `--from-workspace-root <root>`, or `--from-unknown-owner` for `workspace:unknown`; add `--to-fleet <id>` for a Fleet owner |
|
|
670
|
+
| Stale foreign entry already covered by the Fleet manifest | `ownership retire-stale` |
|
|
671
|
+
|
|
672
|
+
`handoff` refuses a file whose runtime hash differs from the manifest. If the drift is reviewed and
|
|
673
|
+
harmless, add `--carry-drift`; applying then also needs `--expected-runtime-hash` from the dry-run.
|
|
674
|
+
The recorded hash is kept, so the next install reports `DRIFT` for that file. Replacing it needs
|
|
675
|
+
`install --agent <agent> --force-drift`, which also force-refreshes every merge contribution of that
|
|
676
|
+
target (for example hooks): check that each merge destination is byte-identical afterwards. Never
|
|
677
|
+
narrow that install with `--select`: it removes unselected merge contributions.
|
package/dist/apply-QJHTL7IK.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
applyCombinedInstallPlan,
|
|
4
|
-
applyInstallPlan,
|
|
5
|
-
commitManifestMetadataJournal,
|
|
6
|
-
recoverPendingApply,
|
|
7
|
-
uninstall
|
|
8
|
-
} from "./chunk-L37KQVRS.js";
|
|
9
|
-
import "./chunk-BHT57MK5.js";
|
|
10
|
-
import "./chunk-BNIF3FUR.js";
|
|
11
|
-
export {
|
|
12
|
-
applyCombinedInstallPlan,
|
|
13
|
-
applyInstallPlan,
|
|
14
|
-
commitManifestMetadataJournal,
|
|
15
|
-
recoverPendingApply,
|
|
16
|
-
uninstall
|
|
17
|
-
};
|