@keygraph/shannon 1.2.0 → 1.4.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/dist/index.mjs +71 -1
- package/infra/compose.yml +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -150,6 +150,72 @@ function addHostFlag() {
|
|
|
150
150
|
return [];
|
|
151
151
|
}
|
|
152
152
|
/**
|
|
153
|
+
* Names whose standard IPs aren't covered by `shouldSkipHostsIp`. Loopback names
|
|
154
|
+
* stay because their IPs (127.x, ::1) get rewritten — not skipped. Others like
|
|
155
|
+
* `broadcasthost` and `ip6-mcastprefix` are intentionally omitted: their IPs
|
|
156
|
+
* (255.255.255.255, ff00::/8) are already dropped at the IP filter.
|
|
157
|
+
*/
|
|
158
|
+
const HOSTS_SKIP_NAMES = new Set([
|
|
159
|
+
"localhost",
|
|
160
|
+
"ip6-localhost",
|
|
161
|
+
"ip6-loopback",
|
|
162
|
+
"ip6-localnet",
|
|
163
|
+
"host.docker.internal",
|
|
164
|
+
"gateway.docker.internal",
|
|
165
|
+
"kubernetes.docker.internal"
|
|
166
|
+
]);
|
|
167
|
+
function isLoopbackIp(ip) {
|
|
168
|
+
return ip.startsWith("127.") || ip === "::1";
|
|
169
|
+
}
|
|
170
|
+
function shouldSkipHostsIp(ip) {
|
|
171
|
+
if (ip === "0.0.0.0" || ip === "255.255.255.255") return true;
|
|
172
|
+
if (ip.startsWith("169.254.")) return true;
|
|
173
|
+
const lower = ip.toLowerCase();
|
|
174
|
+
if (lower.startsWith("fe80:") || lower.startsWith("ff")) return true;
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
function shouldSkipHostsName(name, hostname) {
|
|
178
|
+
const lower = name.toLowerCase();
|
|
179
|
+
if (HOSTS_SKIP_NAMES.has(lower)) return true;
|
|
180
|
+
if (lower === hostname.toLowerCase()) return true;
|
|
181
|
+
if (lower.endsWith(".localhost")) return true;
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Read the host's /etc/hosts and emit --add-host flags so the worker resolves
|
|
186
|
+
* user-added entries the same way. Loopback IPs (127.x, ::1) are rewritten to
|
|
187
|
+
* `host-gateway` so they target the host's loopback instead of the container's.
|
|
188
|
+
*/
|
|
189
|
+
function forwardEtcHostsFlags() {
|
|
190
|
+
if (process.env.SHANNON_FORWARD_HOSTS === "false") return [];
|
|
191
|
+
if (os.platform() === "win32") return [];
|
|
192
|
+
let content;
|
|
193
|
+
try {
|
|
194
|
+
content = fs.readFileSync("/etc/hosts", "utf-8");
|
|
195
|
+
} catch {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const hostname = os.hostname();
|
|
199
|
+
const flags = [];
|
|
200
|
+
for (const rawLine of content.split("\n")) {
|
|
201
|
+
const hashIdx = rawLine.indexOf("#");
|
|
202
|
+
const line = (hashIdx >= 0 ? rawLine.slice(0, hashIdx) : rawLine).trim();
|
|
203
|
+
if (!line) continue;
|
|
204
|
+
const tokens = line.split(" ").flatMap((t) => t.split(" ")).filter(Boolean);
|
|
205
|
+
const ip = tokens[0];
|
|
206
|
+
const names = tokens.slice(1);
|
|
207
|
+
if (!ip || names.length === 0) continue;
|
|
208
|
+
if (shouldSkipHostsIp(ip)) continue;
|
|
209
|
+
const targetIp = isLoopbackIp(ip) ? "host-gateway" : ip;
|
|
210
|
+
const formattedIp = targetIp.includes(":") ? `[${targetIp}]` : targetIp;
|
|
211
|
+
for (const name of names) {
|
|
212
|
+
if (shouldSkipHostsName(name, hostname)) continue;
|
|
213
|
+
flags.push("--add-host", `${name}:${formattedIp}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return flags;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
153
219
|
* Spawn the worker container in detached mode and return the process.
|
|
154
220
|
* When `opts.debug` is true, omits `--rm` so the container persists for log inspection.
|
|
155
221
|
*/
|
|
@@ -158,6 +224,7 @@ function spawnWorker(opts) {
|
|
|
158
224
|
if (!opts.debug) args.push("--rm");
|
|
159
225
|
args.push("--name", opts.containerName, "--network", "shannon-net");
|
|
160
226
|
args.push(...addHostFlag());
|
|
227
|
+
args.push(...forwardEtcHostsFlags());
|
|
161
228
|
if (os.platform() === "linux" && process.getuid && process.getgid) args.push("-e", `SHANNON_HOST_UID=${process.getuid()}`, "-e", `SHANNON_HOST_GID=${process.getgid()}`);
|
|
162
229
|
args.push("-v", `${opts.workspacesDir}:/app/workspaces`);
|
|
163
230
|
args.push("-v", `${opts.repo.hostPath}:${opts.repo.containerPath}:ro`);
|
|
@@ -165,6 +232,7 @@ function spawnWorker(opts) {
|
|
|
165
232
|
args.push("-v", `${path.join(workspacePath, "deliverables")}:${opts.repo.containerPath}/.shannon/deliverables`);
|
|
166
233
|
args.push("-v", `${path.join(workspacePath, "scratchpad")}:${opts.repo.containerPath}/.shannon/scratchpad`);
|
|
167
234
|
args.push("-v", `${path.join(workspacePath, ".playwright-cli")}:${opts.repo.containerPath}/.shannon/.playwright-cli`);
|
|
235
|
+
args.push("-v", `${path.join(workspacePath, ".playwright")}:${opts.repo.containerPath}/.playwright`);
|
|
168
236
|
if (opts.promptsDir) args.push("-v", `${opts.promptsDir}:/app/apps/worker/prompts:ro`);
|
|
169
237
|
if (opts.config) args.push("-v", `${opts.config.hostPath}:${opts.config.containerPath}:ro`);
|
|
170
238
|
if (opts.outputDir) args.push("-v", `${opts.outputDir}:/app/output`);
|
|
@@ -1191,7 +1259,8 @@ async function start(args) {
|
|
|
1191
1259
|
for (const dir of [
|
|
1192
1260
|
"deliverables",
|
|
1193
1261
|
"scratchpad",
|
|
1194
|
-
".playwright-cli"
|
|
1262
|
+
".playwright-cli",
|
|
1263
|
+
".playwright"
|
|
1195
1264
|
]) {
|
|
1196
1265
|
const dirPath = path.join(workspacePath, dir);
|
|
1197
1266
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
@@ -1203,6 +1272,7 @@ async function start(args) {
|
|
|
1203
1272
|
"scratchpad",
|
|
1204
1273
|
".playwright-cli"
|
|
1205
1274
|
]) fs.mkdirSync(path.join(shannonDir, dir), { recursive: true });
|
|
1275
|
+
fs.mkdirSync(path.join(repo.hostPath, ".playwright"), { recursive: true });
|
|
1206
1276
|
const credentialsPath = getCredentialsPath();
|
|
1207
1277
|
const hasCredentials = fs.existsSync(credentialsPath);
|
|
1208
1278
|
if (hasCredentials) process.env.GOOGLE_APPLICATION_CREDENTIALS = "/app/credentials/google-sa-key.json";
|
package/infra/compose.yml
CHANGED