agentwheel 0.20.20 → 0.21.2

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.
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.20.20",
4
+ "version": "0.21.2",
5
5
  "provides": [
6
6
  { "type": "skills", "path": "skills" }
7
7
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.20.20",
3
+ "version": "0.21.2",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.20.20"
8
+ version: "0.21.2"
9
9
  ---
10
10
 
11
11
  # agentwheel
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.20.20"
8
+ version: "0.21.2"
9
9
  ---
10
10
 
11
11
  # Agentwheel Discovery
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- applyCombinedInstallPlan,
4
- applyInstallPlan,
5
- commitManifestMetadataJournal,
6
- recoverPendingApply,
7
- uninstall
8
- } from "./chunk-45WVSUJA.js";
9
- import "./chunk-FGFPAZ4J.js";
10
- import "./chunk-BNIF3FUR.js";
11
- export {
12
- applyCombinedInstallPlan,
13
- applyInstallPlan,
14
- commitManifestMetadataJournal,
15
- recoverPendingApply,
16
- uninstall
17
- };