dahrk-node 0.1.9 → 0.1.11
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/main.js +861 -172
- package/package.json +4 -4
package/dist/main.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/main.ts
|
|
4
|
-
import { execFileSync as
|
|
5
|
-
import { existsSync as
|
|
4
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
5
|
+
import { existsSync as existsSync13, realpathSync as realpathSync5 } from "fs";
|
|
6
6
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
7
|
-
import { homedir as
|
|
8
|
-
import { basename as basename2, join as
|
|
7
|
+
import { homedir as homedir7, platform as osPlatform4 } from "os";
|
|
8
|
+
import { basename as basename2, join as join16 } from "path";
|
|
9
9
|
import { pathToFileURL } from "url";
|
|
10
10
|
|
|
11
11
|
// ../../packages/edge/src/ws-client.ts
|
|
@@ -67,6 +67,8 @@ function createMockRunner(runtime) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// ../../packages/executor-worktree/src/claude-adapter.ts
|
|
70
|
+
import { homedir, tmpdir } from "os";
|
|
71
|
+
import { join as join2 } from "path";
|
|
70
72
|
import {
|
|
71
73
|
query
|
|
72
74
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -336,7 +338,7 @@ var ManagedMailbox = class {
|
|
|
336
338
|
const v = this.q.shift();
|
|
337
339
|
if (v !== void 0) return Promise.resolve({ value: v, done: false });
|
|
338
340
|
if (this.done) return Promise.resolve({ value: void 0, done: true });
|
|
339
|
-
return new Promise((
|
|
341
|
+
return new Promise((resolve3) => this.waiters.push(resolve3));
|
|
340
342
|
}
|
|
341
343
|
};
|
|
342
344
|
}
|
|
@@ -347,7 +349,7 @@ function interactiveIdleWindows(ctx) {
|
|
|
347
349
|
return { firstReplyMs: Math.max(firstReplyMs, idleMs), idleMs };
|
|
348
350
|
}
|
|
349
351
|
function raceNextTurn(pending, idleMs, signal) {
|
|
350
|
-
return new Promise((
|
|
352
|
+
return new Promise((resolve3) => {
|
|
351
353
|
let settled = false;
|
|
352
354
|
let timer;
|
|
353
355
|
function finish(r) {
|
|
@@ -355,7 +357,7 @@ function raceNextTurn(pending, idleMs, signal) {
|
|
|
355
357
|
settled = true;
|
|
356
358
|
if (timer) clearTimeout(timer);
|
|
357
359
|
signal.removeEventListener("abort", onAbort);
|
|
358
|
-
|
|
360
|
+
resolve3(r);
|
|
359
361
|
}
|
|
360
362
|
function onAbort() {
|
|
361
363
|
finish({ kind: "cancelled" });
|
|
@@ -391,7 +393,7 @@ function createElicitTurnRouter(turns, opts) {
|
|
|
391
393
|
const ask = (firstReply, onRaise) => {
|
|
392
394
|
if (ref.settle) return Promise.resolve({ kind: "busy" });
|
|
393
395
|
onRaise();
|
|
394
|
-
return new Promise((
|
|
396
|
+
return new Promise((resolve3) => {
|
|
395
397
|
let settled = false;
|
|
396
398
|
const finish = (o) => {
|
|
397
399
|
if (settled) return;
|
|
@@ -399,7 +401,7 @@ function createElicitTurnRouter(turns, opts) {
|
|
|
399
401
|
clearTimeout(timer);
|
|
400
402
|
opts.signal.removeEventListener("abort", onAbort);
|
|
401
403
|
ref.settle = null;
|
|
402
|
-
|
|
404
|
+
resolve3(o);
|
|
403
405
|
};
|
|
404
406
|
const onAbort = () => finish({ kind: "cancel" });
|
|
405
407
|
const timer = setTimeout(() => finish({ kind: "noreply" }), firstReply ? opts.firstReplyMs : opts.idleMs);
|
|
@@ -518,6 +520,22 @@ function buildBrokeredMcpServers(ctx) {
|
|
|
518
520
|
for (const s of servers) entries[s.id] = { type: s.type, url: `${ctx.mcpProxyBaseUrl}/${s.id}` };
|
|
519
521
|
return entries;
|
|
520
522
|
}
|
|
523
|
+
function sandboxOptions(ctx) {
|
|
524
|
+
if (process.env.DAHRK_SANDBOX !== "1") return {};
|
|
525
|
+
const home = homedir();
|
|
526
|
+
return {
|
|
527
|
+
sandbox: {
|
|
528
|
+
enabled: true,
|
|
529
|
+
failIfUnavailable: false,
|
|
530
|
+
autoAllowBashIfSandboxed: false,
|
|
531
|
+
allowUnsandboxedCommands: false,
|
|
532
|
+
filesystem: {
|
|
533
|
+
allowWrite: [ctx.workspace.worktreePath, ctx.workspace.scratchPath, tmpdir()],
|
|
534
|
+
denyRead: [join2(home, ".ssh"), join2(home, ".aws"), join2(home, ".gnupg"), "/Volumes"]
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
521
539
|
function createClaudeRunner() {
|
|
522
540
|
const abortController = new AbortController();
|
|
523
541
|
let cancelled = false;
|
|
@@ -533,7 +551,25 @@ function createClaudeRunner() {
|
|
|
533
551
|
// is a Claude Code SETTINGS key (not a top-level Options field), so it rides the inline `settings`
|
|
534
552
|
// object, which sits at the flag layer and overrides any project/local setting. Mirrors the cyrus
|
|
535
553
|
// reference (EdgeWorker.ts writes the same key into .claude/settings.local.json).
|
|
536
|
-
settings: {
|
|
554
|
+
settings: {
|
|
555
|
+
includeCoAuthoredBy: false,
|
|
556
|
+
// DHK-392, defence in depth. The stage runner's `fs_confine` builtin is the real block (it is
|
|
557
|
+
// what `canUseTool` below consults, and unlike these rules it covers Grep and Bash). These deny
|
|
558
|
+
// rules close the same door from inside Claude Code, for the tools its permission system does
|
|
559
|
+
// cover (Read/Glob/NotebookRead via `Read(...)`, Write/Edit via `Edit(...)`): credentials an
|
|
560
|
+
// agent has no business reading, and the mounted volumes whose scan started all this. Deny
|
|
561
|
+
// outranks allow, so a repo's own .claude/settings.json cannot undo them.
|
|
562
|
+
permissions: {
|
|
563
|
+
deny: [
|
|
564
|
+
"Read(//Volumes/**)",
|
|
565
|
+
"Read(~/.ssh/**)",
|
|
566
|
+
"Read(~/.aws/**)",
|
|
567
|
+
"Read(~/.gnupg/**)",
|
|
568
|
+
"Read(~/Library/Keychains/**)"
|
|
569
|
+
]
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
...sandboxOptions(ctx),
|
|
537
573
|
// Inherit the REPO's .mcp.json / .claude settings (build spec section 9): do NOT set
|
|
538
574
|
// strictMcpConfig. Policy enforcement around tools is M6; M4 allows tools to run and the
|
|
539
575
|
// stage runner intercepts denied actions at the trace level.
|
|
@@ -1332,8 +1368,8 @@ var PROVIDER_BY_ENV = {
|
|
|
1332
1368
|
// ../../packages/executor-worktree/src/git-service.ts
|
|
1333
1369
|
import { execFileSync } from "child_process";
|
|
1334
1370
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
1335
|
-
import { homedir, tmpdir } from "os";
|
|
1336
|
-
import { basename, dirname, isAbsolute, join as
|
|
1371
|
+
import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
|
|
1372
|
+
import { basename, dirname, isAbsolute, join as join3 } from "path";
|
|
1337
1373
|
var noopLogger = { info: () => {
|
|
1338
1374
|
}, warn: () => {
|
|
1339
1375
|
} };
|
|
@@ -1347,10 +1383,10 @@ function sanitizeBranchName(name) {
|
|
|
1347
1383
|
return name.replace(/[`~^:?*[\]\\@{}\s]/g, "-").replace(/\.{2,}/g, ".").replace(/\/{2,}/g, "/").replace(/\.lock(\/|$)/g, "$1").replace(/^[.\-/]+/, "").replace(/[.\-/]+$/, "").replace(/-{2,}/g, "-");
|
|
1348
1384
|
}
|
|
1349
1385
|
function resolveWorktreesDir(override) {
|
|
1350
|
-
return override ?? process.env.DAHRK_WORKTREES_DIR ?? process.env.SKAKEL_WORKTREES_DIR ??
|
|
1386
|
+
return override ?? process.env.DAHRK_WORKTREES_DIR ?? process.env.SKAKEL_WORKTREES_DIR ?? join3(homedir2(), ".dahrk", "worktrees");
|
|
1351
1387
|
}
|
|
1352
1388
|
function resolveMirrorsDir(override) {
|
|
1353
|
-
return override ?? process.env.DAHRK_MIRRORS_DIR ?? process.env.SKAKEL_MIRRORS_DIR ??
|
|
1389
|
+
return override ?? process.env.DAHRK_MIRRORS_DIR ?? process.env.SKAKEL_MIRRORS_DIR ?? join3(homedir2(), ".dahrk", "mirrors");
|
|
1354
1390
|
}
|
|
1355
1391
|
function createGitService(opts = {}) {
|
|
1356
1392
|
const worktreesDir = resolveWorktreesDir(opts.worktreesDir);
|
|
@@ -1389,12 +1425,12 @@ function createGitService(opts = {}) {
|
|
|
1389
1425
|
const entry = `${SCRATCH_DIR}/`;
|
|
1390
1426
|
try {
|
|
1391
1427
|
const rel = git(worktreePath, ["rev-parse", "--git-path", "info/exclude"]).trim();
|
|
1392
|
-
const excludePath = isAbsolute(rel) ? rel :
|
|
1428
|
+
const excludePath = isAbsolute(rel) ? rel : join3(worktreePath, rel);
|
|
1393
1429
|
const existing = existsSync(excludePath) ? readFileSync2(excludePath, "utf-8") : "";
|
|
1394
1430
|
if (existing.split("\n").some((l) => l.trim() === entry)) return;
|
|
1395
1431
|
mkdirSync(dirname(excludePath), { recursive: true });
|
|
1396
|
-
const
|
|
1397
|
-
writeFileSync(excludePath, `${existing}${
|
|
1432
|
+
const sep4 = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
1433
|
+
writeFileSync(excludePath, `${existing}${sep4}${entry}
|
|
1398
1434
|
`);
|
|
1399
1435
|
} catch (e) {
|
|
1400
1436
|
log.warn(`could not set worktree scratch exclude at ${worktreePath}: ${e.message}`);
|
|
@@ -1419,8 +1455,8 @@ function createGitService(opts = {}) {
|
|
|
1419
1455
|
return { headSha: git(worktreePath, ["rev-parse", "HEAD"]).trim(), dirty };
|
|
1420
1456
|
};
|
|
1421
1457
|
const setupAuth = (token) => {
|
|
1422
|
-
const dir = mkdtempSync(
|
|
1423
|
-
const script =
|
|
1458
|
+
const dir = mkdtempSync(join3(tmpdir2(), "dahrk-cred-"));
|
|
1459
|
+
const script = join3(dir, "askpass.sh");
|
|
1424
1460
|
writeFileSync(script, '#!/bin/sh\nprintf "%s" "$DAHRK_GIT_TOKEN"\n', { mode: 448 });
|
|
1425
1461
|
return {
|
|
1426
1462
|
env: { ...process.env, GIT_ASKPASS: script, DAHRK_GIT_TOKEN: token, GIT_TERMINAL_PROMPT: "0" },
|
|
@@ -1429,7 +1465,7 @@ function createGitService(opts = {}) {
|
|
|
1429
1465
|
};
|
|
1430
1466
|
const netEnv = (authEnv) => authEnv ?? { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
1431
1467
|
const withTokenUser = (gitUrl) => /^https:\/\/[^@/]+@/.test(gitUrl) ? gitUrl : gitUrl.replace(/^https:\/\//, "https://x-access-token@");
|
|
1432
|
-
const mirrorPathFor = (repoId) =>
|
|
1468
|
+
const mirrorPathFor = (repoId) => join3(mirrorsDir, sanitizeBranchName(repoId));
|
|
1433
1469
|
const listWorktrees = (mirror) => {
|
|
1434
1470
|
let out;
|
|
1435
1471
|
try {
|
|
@@ -1540,18 +1576,18 @@ function createGitService(opts = {}) {
|
|
|
1540
1576
|
repo: spec.repo ?? spec.repoId,
|
|
1541
1577
|
baseBranch: spec.baseBranch,
|
|
1542
1578
|
worktreePath,
|
|
1543
|
-
scratchPath:
|
|
1579
|
+
scratchPath: join3(worktreePath, ".skakel", "scratch")
|
|
1544
1580
|
});
|
|
1545
1581
|
return {
|
|
1546
1582
|
worktreesDir,
|
|
1547
1583
|
async createWorktree(spec) {
|
|
1548
1584
|
const { repoId, gitUrl, baseBranch, runId } = spec;
|
|
1549
1585
|
const branchName = sanitizeBranchName(spec.branch ?? `dahrk/${runId}`);
|
|
1550
|
-
const worktreePath =
|
|
1586
|
+
const worktreePath = join3(worktreesDir, runId);
|
|
1551
1587
|
mkdirSync(worktreesDir, { recursive: true });
|
|
1552
1588
|
if (existsSync(worktreePath) && gitOk2(worktreePath, ["rev-parse", "--git-dir"])) {
|
|
1553
1589
|
log.info(`reusing existing worktree at ${worktreePath}`);
|
|
1554
|
-
mkdirSync(
|
|
1590
|
+
mkdirSync(join3(worktreePath, ".skakel", "scratch"), { recursive: true });
|
|
1555
1591
|
return refFor(spec, worktreePath);
|
|
1556
1592
|
}
|
|
1557
1593
|
const auth = spec.credentialToken ? setupAuth(spec.credentialToken) : void 0;
|
|
@@ -1587,7 +1623,7 @@ function createGitService(opts = {}) {
|
|
|
1587
1623
|
if (!gitOk2(worktreePath, ["rev-parse", "--verify", "-q", "HEAD"])) {
|
|
1588
1624
|
throw new Error(`base '${baseBranch}' did not materialise into ${worktreePath} (unborn HEAD)`);
|
|
1589
1625
|
}
|
|
1590
|
-
mkdirSync(
|
|
1626
|
+
mkdirSync(join3(worktreePath, ".skakel", "scratch"), { recursive: true });
|
|
1591
1627
|
return refFor(spec, worktreePath);
|
|
1592
1628
|
},
|
|
1593
1629
|
async commitAndPush(ref, opts2) {
|
|
@@ -1693,8 +1729,8 @@ function createGitService(opts = {}) {
|
|
|
1693
1729
|
return void 0;
|
|
1694
1730
|
}
|
|
1695
1731
|
};
|
|
1696
|
-
const tmp = mkdtempSync(
|
|
1697
|
-
const bodyFile =
|
|
1732
|
+
const tmp = mkdtempSync(join3(tmpdir2(), "dahrk-pr-"));
|
|
1733
|
+
const bodyFile = join3(tmp, "body.md");
|
|
1698
1734
|
try {
|
|
1699
1735
|
writeFileSync(bodyFile, opts2.body ?? "");
|
|
1700
1736
|
try {
|
|
@@ -1730,7 +1766,7 @@ function createGitService(opts = {}) {
|
|
|
1730
1766
|
// ../../packages/executor-worktree/src/worktree-reaper.ts
|
|
1731
1767
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1732
1768
|
import { existsSync as existsSync2, readdirSync, realpathSync, rmSync as rmSync2, statSync } from "fs";
|
|
1733
|
-
import { join as
|
|
1769
|
+
import { join as join4 } from "path";
|
|
1734
1770
|
var MINUTE = 6e4;
|
|
1735
1771
|
var HOUR = 60 * MINUTE;
|
|
1736
1772
|
var DEFAULTS = {
|
|
@@ -1760,7 +1796,7 @@ var canonical = (p) => {
|
|
|
1760
1796
|
}
|
|
1761
1797
|
};
|
|
1762
1798
|
function lastUsedMs(worktreePath) {
|
|
1763
|
-
const candidates = [
|
|
1799
|
+
const candidates = [join4(worktreePath, ".skakel", "scratch", "state.json"), worktreePath];
|
|
1764
1800
|
let newest = 0;
|
|
1765
1801
|
for (const p of candidates) {
|
|
1766
1802
|
try {
|
|
@@ -1783,7 +1819,7 @@ function createWorktreeReaper(opts) {
|
|
|
1783
1819
|
const log = opts.logger ?? noop;
|
|
1784
1820
|
const mirrors = () => {
|
|
1785
1821
|
try {
|
|
1786
|
-
return readdirSync(opts.mirrorsDir).map((d) =>
|
|
1822
|
+
return readdirSync(opts.mirrorsDir).map((d) => join4(opts.mirrorsDir, d)).filter((m) => gitOk(m, ["rev-parse", "--git-dir"]));
|
|
1787
1823
|
} catch {
|
|
1788
1824
|
return [];
|
|
1789
1825
|
}
|
|
@@ -1807,7 +1843,7 @@ function createWorktreeReaper(opts) {
|
|
|
1807
1843
|
}
|
|
1808
1844
|
const onDisk = (() => {
|
|
1809
1845
|
try {
|
|
1810
|
-
return readdirSync(opts.worktreesDir).map((d) => canonical(
|
|
1846
|
+
return readdirSync(opts.worktreesDir).map((d) => canonical(join4(opts.worktreesDir, d)));
|
|
1811
1847
|
} catch {
|
|
1812
1848
|
return [];
|
|
1813
1849
|
}
|
|
@@ -1868,15 +1904,15 @@ function createWorktreeReaper(opts) {
|
|
|
1868
1904
|
// ../../packages/executor-worktree/src/trace-writer.ts
|
|
1869
1905
|
import { createHash } from "crypto";
|
|
1870
1906
|
import { appendFileSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1871
|
-
import { join as
|
|
1907
|
+
import { join as join5 } from "path";
|
|
1872
1908
|
var DEFAULT_SPILL_BYTES = 8192;
|
|
1873
1909
|
function createTraceWriter(scratchPath, meta, opts = {}) {
|
|
1874
1910
|
const spillBytes = opts.spillBytes ?? DEFAULT_SPILL_BYTES;
|
|
1875
|
-
const dir =
|
|
1876
|
-
mkdirSync2(
|
|
1877
|
-
mkdirSync2(
|
|
1878
|
-
const tracePath =
|
|
1879
|
-
const metaPath =
|
|
1911
|
+
const dir = join5(scratchPath, "traces", meta.stageId, `attempt-${meta.attempt}`);
|
|
1912
|
+
mkdirSync2(join5(dir, "blobs"), { recursive: true });
|
|
1913
|
+
mkdirSync2(join5(dir, "raw"), { recursive: true });
|
|
1914
|
+
const tracePath = join5(dir, "trace.jsonl");
|
|
1915
|
+
const metaPath = join5(dir, "meta.json");
|
|
1880
1916
|
let current = { ...meta };
|
|
1881
1917
|
let nextSeq = 0;
|
|
1882
1918
|
let rawCount = 0;
|
|
@@ -1888,8 +1924,8 @@ function createTraceWriter(scratchPath, meta, opts = {}) {
|
|
|
1888
1924
|
const spillValue = (value) => {
|
|
1889
1925
|
const data = typeof value === "string" ? value : JSON.stringify(value);
|
|
1890
1926
|
const sha = createHash("sha256").update(data).digest("hex");
|
|
1891
|
-
writeFileSync2(
|
|
1892
|
-
return
|
|
1927
|
+
writeFileSync2(join5(dir, "blobs", sha), data);
|
|
1928
|
+
return join5("blobs", sha);
|
|
1893
1929
|
};
|
|
1894
1930
|
const spill = (event) => {
|
|
1895
1931
|
if (event.type === "thought" && event.text !== void 0 && tooBig(event.text)) {
|
|
@@ -1919,8 +1955,8 @@ function createTraceWriter(scratchPath, meta, opts = {}) {
|
|
|
1919
1955
|
return written;
|
|
1920
1956
|
},
|
|
1921
1957
|
writeRaw(record) {
|
|
1922
|
-
const rel =
|
|
1923
|
-
writeFileSync2(
|
|
1958
|
+
const rel = join5("raw", `${rawCount++}.json`);
|
|
1959
|
+
writeFileSync2(join5(dir, rel), JSON.stringify(record, null, 2));
|
|
1924
1960
|
return rel;
|
|
1925
1961
|
},
|
|
1926
1962
|
finalise(patch = {}) {
|
|
@@ -1936,12 +1972,12 @@ function createTraceWriter(scratchPath, meta, opts = {}) {
|
|
|
1936
1972
|
// ../../packages/executor-worktree/src/pack-cache.ts
|
|
1937
1973
|
import { createHash as createHash2 } from "crypto";
|
|
1938
1974
|
import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1939
|
-
import { dirname as dirname2, join as
|
|
1975
|
+
import { dirname as dirname2, join as join6, relative, sep } from "path";
|
|
1940
1976
|
function readManifestFiles(dir) {
|
|
1941
1977
|
const out = [];
|
|
1942
1978
|
const walk = (cur) => {
|
|
1943
1979
|
for (const entry of readdirSync2(cur, { withFileTypes: true })) {
|
|
1944
|
-
const abs =
|
|
1980
|
+
const abs = join6(cur, entry.name);
|
|
1945
1981
|
if (entry.isDirectory()) walk(abs);
|
|
1946
1982
|
else out.push(relative(dir, abs).split(sep).join("/"));
|
|
1947
1983
|
}
|
|
@@ -1952,7 +1988,7 @@ function readManifestFiles(dir) {
|
|
|
1952
1988
|
|
|
1953
1989
|
// ../../packages/executor-worktree/src/overlay.ts
|
|
1954
1990
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
1955
|
-
import { dirname as dirname3, join as
|
|
1991
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
1956
1992
|
function sameBytes(dest, bytes) {
|
|
1957
1993
|
try {
|
|
1958
1994
|
return readFileSync3(dest).equals(bytes);
|
|
@@ -1972,8 +2008,8 @@ async function overlayComponents(opts) {
|
|
|
1972
2008
|
}
|
|
1973
2009
|
const { dir } = await cache.materialise(ref);
|
|
1974
2010
|
for (const relPath of readManifestFiles(dir)) {
|
|
1975
|
-
const src =
|
|
1976
|
-
const dest =
|
|
2011
|
+
const src = join7(dir, relPath);
|
|
2012
|
+
const dest = join7(worktreePath, relPath);
|
|
1977
2013
|
const bytes = readFileSync3(src);
|
|
1978
2014
|
if (existsSync4(dest)) {
|
|
1979
2015
|
if (sameBytes(dest, bytes)) continue;
|
|
@@ -2004,9 +2040,163 @@ function makeRunner(runtime) {
|
|
|
2004
2040
|
return runtime === "codex" ? createCodexRunner() : createClaudeRunner();
|
|
2005
2041
|
}
|
|
2006
2042
|
|
|
2043
|
+
// ../../packages/edge/src/health.ts
|
|
2044
|
+
import { statfsSync } from "fs";
|
|
2045
|
+
var HealthCounters = class {
|
|
2046
|
+
startedAt = Date.now();
|
|
2047
|
+
errors = /* @__PURE__ */ new Map();
|
|
2048
|
+
crashes = 0;
|
|
2049
|
+
/** Connections made this process lifetime. The flapping signal: this climbing while uptime ALSO climbs
|
|
2050
|
+
* means the socket keeps dropping under a process that is otherwise fine. */
|
|
2051
|
+
connectCount = 0;
|
|
2052
|
+
/** Jobs in flight. A node stuck at 1 for an hour is wedged, and nothing else reveals it. */
|
|
2053
|
+
activeJobs = 0;
|
|
2054
|
+
/** Worktrees on disk. Climbing without bound was the shape of the leak DHK-371 fixed. */
|
|
2055
|
+
worktreeCount = 0;
|
|
2056
|
+
/** Bucket a stage failure. The CLASS only - the message would carry their paths. */
|
|
2057
|
+
recordError(kind) {
|
|
2058
|
+
this.errors.set(kind, (this.errors.get(kind) ?? 0) + 1);
|
|
2059
|
+
}
|
|
2060
|
+
recordCrash() {
|
|
2061
|
+
this.crashes++;
|
|
2062
|
+
}
|
|
2063
|
+
uptimeSec() {
|
|
2064
|
+
return Math.round((Date.now() - this.startedAt) / 1e3);
|
|
2065
|
+
}
|
|
2066
|
+
errorCounts() {
|
|
2067
|
+
return Object.fromEntries(this.errors);
|
|
2068
|
+
}
|
|
2069
|
+
crashCount() {
|
|
2070
|
+
return this.crashes;
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
function diskFreeBytes(path) {
|
|
2074
|
+
try {
|
|
2075
|
+
const st = statfsSync(path);
|
|
2076
|
+
return Number(st.bfree) * Number(st.bsize);
|
|
2077
|
+
} catch {
|
|
2078
|
+
return void 0;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
function collectHealth(inputs) {
|
|
2082
|
+
const { counters } = inputs;
|
|
2083
|
+
const free = diskFreeBytes(inputs.worktreesDir);
|
|
2084
|
+
const errors = counters.errorCounts();
|
|
2085
|
+
return {
|
|
2086
|
+
uptimeSec: counters.uptimeSec(),
|
|
2087
|
+
clientVersion: inputs.clientVersion,
|
|
2088
|
+
activeJobs: counters.activeJobs,
|
|
2089
|
+
connectCount: counters.connectCount,
|
|
2090
|
+
worktreeCount: counters.worktreeCount,
|
|
2091
|
+
...free !== void 0 ? { diskFreeBytes: free } : {},
|
|
2092
|
+
runtimes: inputs.runtimes,
|
|
2093
|
+
...Object.keys(errors).length > 0 ? { errors } : {},
|
|
2094
|
+
...counters.crashCount() > 0 ? { crashes: counters.crashCount() } : {}
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// ../../packages/edge/src/log-shipper.ts
|
|
2099
|
+
import { shouldShip } from "@dahrk/contracts";
|
|
2100
|
+
var SHIP_BUFFER_MAX = 500;
|
|
2101
|
+
var SHIP_BATCH_MAX = 200;
|
|
2102
|
+
var SHIP_FLUSH_MS = 2e3;
|
|
2103
|
+
var LogShipper = class {
|
|
2104
|
+
constructor(opts = {}) {
|
|
2105
|
+
this.opts = opts;
|
|
2106
|
+
this.ceiling = opts.ceiling ?? { health: true, logs: "debug" };
|
|
2107
|
+
this.policy = this.clampToCeiling(opts.initial ?? { health: true, logs: "off" });
|
|
2108
|
+
}
|
|
2109
|
+
opts;
|
|
2110
|
+
buffer = [];
|
|
2111
|
+
policy;
|
|
2112
|
+
ceiling;
|
|
2113
|
+
timer;
|
|
2114
|
+
/** Attached once the socket exists. The shipper is constructed FIRST, because it has to be a pino
|
|
2115
|
+
* stream before the logger is built, which is before the client is started. Until `attach`, records
|
|
2116
|
+
* simply accumulate in the (bounded) ring. */
|
|
2117
|
+
send;
|
|
2118
|
+
/** Records we threw away because the buffer was full. Reported, never hidden. */
|
|
2119
|
+
dropped = 0;
|
|
2120
|
+
/** Give the shipper its transport. Called by the client once the socket exists. */
|
|
2121
|
+
attach(send) {
|
|
2122
|
+
this.send = send;
|
|
2123
|
+
}
|
|
2124
|
+
/** The hub may only ever ask for LESS than the local ceiling allows. */
|
|
2125
|
+
clampToCeiling(p) {
|
|
2126
|
+
const rank = { off: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
2127
|
+
const logs = rank[p.logs] > rank[this.ceiling.logs] ? this.ceiling.logs : p.logs;
|
|
2128
|
+
return { health: p.health && this.ceiling.health, logs };
|
|
2129
|
+
}
|
|
2130
|
+
/** Apply a policy from the hub (`welcome`, or a live `policy` frame). */
|
|
2131
|
+
setPolicy(p) {
|
|
2132
|
+
this.policy = this.clampToCeiling(p);
|
|
2133
|
+
if (this.policy.logs === "off") this.buffer = [];
|
|
2134
|
+
}
|
|
2135
|
+
current() {
|
|
2136
|
+
return this.policy;
|
|
2137
|
+
}
|
|
2138
|
+
/** Offer a record. Cheap and synchronous - this sits on the logging hot path. */
|
|
2139
|
+
offer(record) {
|
|
2140
|
+
if (this.policy.logs === "off") return;
|
|
2141
|
+
if (!shouldShip(record.level, this.policy.logs)) return;
|
|
2142
|
+
this.buffer.push(record);
|
|
2143
|
+
if (this.buffer.length > (this.opts.bufferMax ?? SHIP_BUFFER_MAX)) {
|
|
2144
|
+
this.buffer.shift();
|
|
2145
|
+
this.dropped++;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
/** Send one batch if there is anything to send and the socket will take it. */
|
|
2149
|
+
flush() {
|
|
2150
|
+
if (this.buffer.length === 0 || !this.send) return;
|
|
2151
|
+
const batch = this.buffer.slice(0, SHIP_BATCH_MAX);
|
|
2152
|
+
if (!this.send(batch)) return;
|
|
2153
|
+
this.buffer = this.buffer.slice(batch.length);
|
|
2154
|
+
}
|
|
2155
|
+
start() {
|
|
2156
|
+
if (this.timer) return;
|
|
2157
|
+
this.timer = setInterval(() => this.flush(), this.opts.flushMs ?? SHIP_FLUSH_MS);
|
|
2158
|
+
this.timer.unref?.();
|
|
2159
|
+
}
|
|
2160
|
+
stop() {
|
|
2161
|
+
if (this.timer) clearInterval(this.timer);
|
|
2162
|
+
this.timer = void 0;
|
|
2163
|
+
}
|
|
2164
|
+
/** For the health report and for tests. */
|
|
2165
|
+
pending() {
|
|
2166
|
+
return this.buffer.length;
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
function shipperStream(shipper) {
|
|
2170
|
+
return {
|
|
2171
|
+
write(chunk) {
|
|
2172
|
+
try {
|
|
2173
|
+
const r = JSON.parse(chunk);
|
|
2174
|
+
if (!r.msg) return;
|
|
2175
|
+
shipper.offer({
|
|
2176
|
+
level: r.level,
|
|
2177
|
+
time: r.time,
|
|
2178
|
+
msg: r.msg,
|
|
2179
|
+
...r.runId ? { runId: r.runId } : {},
|
|
2180
|
+
...r.stageId ? { stageId: r.stageId } : {},
|
|
2181
|
+
...r.jobId ? { jobId: r.jobId } : {},
|
|
2182
|
+
...r.component ? { component: r.component } : {},
|
|
2183
|
+
...r.err ? { err: r.err } : {}
|
|
2184
|
+
});
|
|
2185
|
+
} catch {
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
function ceilingFromEnv(env) {
|
|
2191
|
+
const v = env.DAHRK_TELEMETRY?.toLowerCase();
|
|
2192
|
+
if (v === "off" || v === "0" || v === "false") return { health: false, logs: "off" };
|
|
2193
|
+
if (v === "health") return { health: true, logs: "off" };
|
|
2194
|
+
return { health: true, logs: "debug" };
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2007
2197
|
// ../../packages/edge/src/logger.ts
|
|
2008
2198
|
import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync5, openSync, renameSync, rmSync as rmSync4, statSync as statSync2, writeSync } from "fs";
|
|
2009
|
-
import { join as
|
|
2199
|
+
import { join as join8 } from "path";
|
|
2010
2200
|
import pino from "pino";
|
|
2011
2201
|
|
|
2012
2202
|
// ../../packages/edge/src/redact.ts
|
|
@@ -2196,14 +2386,15 @@ function createNodeLogger(opts = {}) {
|
|
|
2196
2386
|
if (opts.dir && fileLevel !== "silent") {
|
|
2197
2387
|
try {
|
|
2198
2388
|
mkdirSync5(opts.dir, { recursive: true, mode: 448 });
|
|
2199
|
-
const file = new RotatingFile(
|
|
2389
|
+
const file = new RotatingFile(join8(opts.dir, "node.jsonl"), opts.maxBytes ?? 10 * 1024 * 1024, opts.maxFiles ?? 5);
|
|
2200
2390
|
streams.push({ level: fileLevel, stream: file });
|
|
2201
2391
|
} catch (e) {
|
|
2202
2392
|
process.stderr.write(`dahrk: file logging disabled (${e.message})
|
|
2203
2393
|
`);
|
|
2204
2394
|
}
|
|
2205
2395
|
}
|
|
2206
|
-
|
|
2396
|
+
if (opts.ship) streams.push({ level: "trace", stream: opts.ship });
|
|
2397
|
+
const base = streams.length === 0 ? "silent" : opts.ship ? "trace" : opts.dir ? lowest(level, fileLevel) : level;
|
|
2207
2398
|
return pino(
|
|
2208
2399
|
{
|
|
2209
2400
|
level: base,
|
|
@@ -2219,12 +2410,13 @@ function createNodeLogger(opts = {}) {
|
|
|
2219
2410
|
pino.multistream(streams)
|
|
2220
2411
|
);
|
|
2221
2412
|
}
|
|
2222
|
-
function createNodeLoggerFromEnv(env, dir, base) {
|
|
2413
|
+
function createNodeLoggerFromEnv(env, dir, base, ship) {
|
|
2223
2414
|
const fileLevel = fileLevelFromEnv(env);
|
|
2224
2415
|
return createNodeLogger({
|
|
2225
2416
|
level: levelFromEnv(env),
|
|
2226
2417
|
...fileLevel === "off" ? {} : { dir, fileLevel },
|
|
2227
|
-
...base ? { base } : {}
|
|
2418
|
+
...base ? { base } : {},
|
|
2419
|
+
...ship ? { ship } : {}
|
|
2228
2420
|
});
|
|
2229
2421
|
}
|
|
2230
2422
|
|
|
@@ -2250,15 +2442,377 @@ function denyToolRule(tool3) {
|
|
|
2250
2442
|
}
|
|
2251
2443
|
|
|
2252
2444
|
// ../../packages/edge/src/stage-runner.ts
|
|
2253
|
-
import { execFileSync as
|
|
2445
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
2254
2446
|
import { createHash as createHash3 } from "crypto";
|
|
2255
2447
|
import { mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync4, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
2256
|
-
import { tmpdir as
|
|
2257
|
-
import { isAbsolute as
|
|
2448
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
2449
|
+
import { isAbsolute as isAbsolute3, join as join10, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
|
|
2258
2450
|
import { attachedDocBasename as attachedDocBasename2 } from "@dahrk/contracts";
|
|
2259
2451
|
|
|
2260
2452
|
// ../../packages/edge/src/builtins.ts
|
|
2453
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
2454
|
+
|
|
2455
|
+
// ../../packages/edge/src/fs-roots.ts
|
|
2261
2456
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2457
|
+
import { existsSync as existsSync6, realpathSync as realpathSync2 } from "fs";
|
|
2458
|
+
import { homedir as homedir3, tmpdir as tmpdir3 } from "os";
|
|
2459
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve, sep as sep2 } from "path";
|
|
2460
|
+
function isUnder(root, target) {
|
|
2461
|
+
const rel = relative2(resolve(root), resolve(target));
|
|
2462
|
+
return rel === "" || !rel.startsWith(`..${sep2}`) && rel !== ".." && !isAbsolute2(rel);
|
|
2463
|
+
}
|
|
2464
|
+
function expandPath(raw, cwd) {
|
|
2465
|
+
const home = homedir3();
|
|
2466
|
+
const expanded = raw.replace(/^~(?=$|\/)/, home).replace(/^\$\{HOME\}(?=$|\/)/, home).replace(/^\$HOME(?=$|\/)/, home);
|
|
2467
|
+
return resolve(cwd, expanded);
|
|
2468
|
+
}
|
|
2469
|
+
function realish(p) {
|
|
2470
|
+
let head = p;
|
|
2471
|
+
const tail = [];
|
|
2472
|
+
while (head !== dirname4(head)) {
|
|
2473
|
+
if (existsSync6(head)) {
|
|
2474
|
+
try {
|
|
2475
|
+
return join9(realpathSync2.native(head), ...tail.reverse());
|
|
2476
|
+
} catch {
|
|
2477
|
+
return p;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
tail.push(head.slice(dirname4(head).length + 1));
|
|
2481
|
+
head = dirname4(head);
|
|
2482
|
+
}
|
|
2483
|
+
return p;
|
|
2484
|
+
}
|
|
2485
|
+
function withinRoots(raw, roots, need) {
|
|
2486
|
+
const literal = expandPath(raw, roots.cwd);
|
|
2487
|
+
const real = realish(literal);
|
|
2488
|
+
const hits = (list2) => list2.some((root) => isUnder(root, literal) || isUnder(root, real));
|
|
2489
|
+
if (hits(roots.deny)) return false;
|
|
2490
|
+
if (hits(roots.rw)) return true;
|
|
2491
|
+
return need === "read" && hits(roots.ro);
|
|
2492
|
+
}
|
|
2493
|
+
function gitCommonDir(worktreePath) {
|
|
2494
|
+
try {
|
|
2495
|
+
const out = execFileSync3("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
2496
|
+
cwd: worktreePath,
|
|
2497
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2498
|
+
}).toString().trim();
|
|
2499
|
+
return out || void 0;
|
|
2500
|
+
} catch {
|
|
2501
|
+
return void 0;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
var pnpmStoreCache;
|
|
2505
|
+
function pnpmStore() {
|
|
2506
|
+
if (pnpmStoreCache) return pnpmStoreCache.path;
|
|
2507
|
+
try {
|
|
2508
|
+
const out = execFileSync3("pnpm", ["store", "path"], { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }).toString().trim();
|
|
2509
|
+
pnpmStoreCache = { path: out || void 0 };
|
|
2510
|
+
} catch {
|
|
2511
|
+
pnpmStoreCache = { path: void 0 };
|
|
2512
|
+
}
|
|
2513
|
+
return pnpmStoreCache.path;
|
|
2514
|
+
}
|
|
2515
|
+
var splitRoots = (v) => (v ?? "").split(":").map((s) => s.trim()).filter(Boolean);
|
|
2516
|
+
function computeFsRoots(opts) {
|
|
2517
|
+
const home = homedir3();
|
|
2518
|
+
const wt = realish(resolve(opts.worktreePath));
|
|
2519
|
+
const rw = [
|
|
2520
|
+
wt,
|
|
2521
|
+
...opts.scratchPath ? [realish(resolve(opts.scratchPath))] : [],
|
|
2522
|
+
// Every git command in the worktree reads and writes here.
|
|
2523
|
+
...[gitCommonDir(opts.worktreePath)].filter((p) => Boolean(p)).map(realish),
|
|
2524
|
+
// Scratch space: `mkdtemp`, git's temp files, and an agent tidying a throwaway dir it created.
|
|
2525
|
+
...[tmpdir3(), "/tmp", "/private/tmp", "/var/folders", "/private/var/folders"].map(realish),
|
|
2526
|
+
// The safe I/O sinks. `2>/dev/null` is on a third of the shell commands real stages run; treating
|
|
2527
|
+
// it as a write outside the worktree would deny most of a normal build. The raw devices are NOT
|
|
2528
|
+
// here, so `> /dev/sda` still fails confinement (as well as shell_guard's device-write regex).
|
|
2529
|
+
...["/dev/null", "/dev/stdout", "/dev/stderr", "/dev/tty", "/dev/fd"],
|
|
2530
|
+
...[pnpmStore()].filter((p) => Boolean(p)).map(realish),
|
|
2531
|
+
...splitRoots(process.env.DAHRK_FS_EXTRA_ROOTS).map((p) => realish(resolve(p)))
|
|
2532
|
+
];
|
|
2533
|
+
const ro = [
|
|
2534
|
+
"/usr",
|
|
2535
|
+
"/bin",
|
|
2536
|
+
"/sbin",
|
|
2537
|
+
"/etc",
|
|
2538
|
+
"/opt",
|
|
2539
|
+
"/nix",
|
|
2540
|
+
"/Library",
|
|
2541
|
+
"/System",
|
|
2542
|
+
"/proc",
|
|
2543
|
+
"/sys",
|
|
2544
|
+
join9(home, ".gitconfig"),
|
|
2545
|
+
join9(home, ".config"),
|
|
2546
|
+
join9(home, ".npmrc"),
|
|
2547
|
+
join9(home, ".cache"),
|
|
2548
|
+
join9(home, "Library", "Caches"),
|
|
2549
|
+
join9(home, "Library", "pnpm"),
|
|
2550
|
+
join9(home, ".local", "share"),
|
|
2551
|
+
join9(home, ".nvm"),
|
|
2552
|
+
join9(home, ".volta"),
|
|
2553
|
+
join9(home, ".asdf"),
|
|
2554
|
+
join9(home, ".cargo"),
|
|
2555
|
+
join9(home, ".rustup"),
|
|
2556
|
+
...splitRoots(process.env.DAHRK_FS_EXTRA_READ_ROOTS).map((p) => realish(resolve(p)))
|
|
2557
|
+
].map(realish);
|
|
2558
|
+
const deny = [
|
|
2559
|
+
join9(home, ".ssh"),
|
|
2560
|
+
join9(home, ".aws"),
|
|
2561
|
+
join9(home, ".gnupg"),
|
|
2562
|
+
join9(home, ".config", "gcloud"),
|
|
2563
|
+
join9(home, "Library", "Keychains"),
|
|
2564
|
+
"/Volumes",
|
|
2565
|
+
"/etc/shadow",
|
|
2566
|
+
"/etc/sudoers"
|
|
2567
|
+
];
|
|
2568
|
+
return { rw, ro, deny, cwd: wt };
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// ../../packages/edge/src/shell-scan.ts
|
|
2572
|
+
var PATTERN_FLAGS = /* @__PURE__ */ new Set([
|
|
2573
|
+
"-e",
|
|
2574
|
+
"--regexp",
|
|
2575
|
+
"-path",
|
|
2576
|
+
"-ipath",
|
|
2577
|
+
"-name",
|
|
2578
|
+
"-iname",
|
|
2579
|
+
"-wholename",
|
|
2580
|
+
"--glob",
|
|
2581
|
+
"-g",
|
|
2582
|
+
"--include",
|
|
2583
|
+
"--exclude",
|
|
2584
|
+
"--exclude-dir",
|
|
2585
|
+
"--type",
|
|
2586
|
+
"-m",
|
|
2587
|
+
"--message"
|
|
2588
|
+
]);
|
|
2589
|
+
var PATTERN_FIRST = /* @__PURE__ */ new Set(["grep", "egrep", "fgrep", "rg", "ag", "ack", "sed", "awk", "perl"]);
|
|
2590
|
+
var DIR_FLAGS = /* @__PURE__ */ new Set(["-C", "--git-dir", "--work-tree", "--cwd", "--directory"]);
|
|
2591
|
+
var NO_PATH_ARGV0 = /* @__PURE__ */ new Set(["echo", "printf", ":", "true", "false"]);
|
|
2592
|
+
var WRITE_ALL = /* @__PURE__ */ new Set(["rm", "rmdir", "mkdir", "touch", "chmod", "chown", "truncate", "mkfifo"]);
|
|
2593
|
+
var WRITE_LAST = /* @__PURE__ */ new Set(["cp", "mv", "ln", "tee", "dd", "install", "rsync"]);
|
|
2594
|
+
var SHELL_ARGV0 = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "eval", "xargs", "env", "nohup", "time", "sudo"]);
|
|
2595
|
+
var REDIRECTS = /* @__PURE__ */ new Set([">", ">>", "<", "2>", "2>>", "&>", "<<<"]);
|
|
2596
|
+
function tokenise(cmd) {
|
|
2597
|
+
const tokens = [];
|
|
2598
|
+
const subshells = [];
|
|
2599
|
+
let cur = "";
|
|
2600
|
+
let quoted = false;
|
|
2601
|
+
let started = false;
|
|
2602
|
+
let quote = null;
|
|
2603
|
+
const push = () => {
|
|
2604
|
+
if (started) tokens.push({ value: cur, quoted });
|
|
2605
|
+
cur = "";
|
|
2606
|
+
quoted = false;
|
|
2607
|
+
started = false;
|
|
2608
|
+
};
|
|
2609
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
2610
|
+
const c = cmd[i];
|
|
2611
|
+
if (quote) {
|
|
2612
|
+
if (c === quote) {
|
|
2613
|
+
quote = null;
|
|
2614
|
+
} else if (c === "\\" && quote === '"' && i + 1 < cmd.length) {
|
|
2615
|
+
cur += cmd[++i];
|
|
2616
|
+
started = true;
|
|
2617
|
+
} else {
|
|
2618
|
+
cur += c;
|
|
2619
|
+
started = true;
|
|
2620
|
+
}
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
if (c === '"' || c === "'") {
|
|
2624
|
+
quote = c;
|
|
2625
|
+
quoted = true;
|
|
2626
|
+
started = true;
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
if (c === "\\" && i + 1 < cmd.length) {
|
|
2630
|
+
cur += cmd[++i];
|
|
2631
|
+
started = true;
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
if (c === "$" && cmd[i + 1] === "(" || c === "`") {
|
|
2635
|
+
const open = c === "`" ? "`" : "(";
|
|
2636
|
+
const close = c === "`" ? "`" : ")";
|
|
2637
|
+
let depth = 1;
|
|
2638
|
+
let j = i + (c === "`" ? 1 : 2);
|
|
2639
|
+
let body = "";
|
|
2640
|
+
for (; j < cmd.length && depth > 0; j++) {
|
|
2641
|
+
const d = cmd[j];
|
|
2642
|
+
if (d === open && close !== "`") depth++;
|
|
2643
|
+
else if (d === close) {
|
|
2644
|
+
depth--;
|
|
2645
|
+
if (depth === 0) break;
|
|
2646
|
+
}
|
|
2647
|
+
body += d;
|
|
2648
|
+
}
|
|
2649
|
+
if (depth > 0) return null;
|
|
2650
|
+
subshells.push(body);
|
|
2651
|
+
cur += "$SUBSHELL";
|
|
2652
|
+
started = true;
|
|
2653
|
+
i = j;
|
|
2654
|
+
continue;
|
|
2655
|
+
}
|
|
2656
|
+
if (/\s/.test(c)) {
|
|
2657
|
+
push();
|
|
2658
|
+
continue;
|
|
2659
|
+
}
|
|
2660
|
+
const three = cmd.slice(i, i + 3);
|
|
2661
|
+
const two = cmd.slice(i, i + 2);
|
|
2662
|
+
if (three === "<<<") {
|
|
2663
|
+
push();
|
|
2664
|
+
tokens.push({ value: three, quoted: false, operator: three });
|
|
2665
|
+
i += 2;
|
|
2666
|
+
continue;
|
|
2667
|
+
}
|
|
2668
|
+
if (["&&", "||", ">>", "2>", "&>"].includes(two)) {
|
|
2669
|
+
push();
|
|
2670
|
+
tokens.push({ value: two, quoted: false, operator: two });
|
|
2671
|
+
i += 1;
|
|
2672
|
+
continue;
|
|
2673
|
+
}
|
|
2674
|
+
if (c === ";" || c === "|" || c === "&" || c === ">" || c === "<" || c === "\n") {
|
|
2675
|
+
push();
|
|
2676
|
+
tokens.push({ value: c, quoted: false, operator: c });
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
cur += c;
|
|
2680
|
+
started = true;
|
|
2681
|
+
}
|
|
2682
|
+
if (quote) return null;
|
|
2683
|
+
push();
|
|
2684
|
+
return { tokens, subshells };
|
|
2685
|
+
}
|
|
2686
|
+
function looksLikePath(t) {
|
|
2687
|
+
if (t.value.startsWith("-")) return false;
|
|
2688
|
+
if (/\s/.test(t.value)) return false;
|
|
2689
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(t.value)) return false;
|
|
2690
|
+
const s = t.value.replace(/^@/, "");
|
|
2691
|
+
if (s.includes("$SUBSHELL")) return false;
|
|
2692
|
+
if (/^\$(?!\{?HOME\b)/.test(s)) return false;
|
|
2693
|
+
return /^(\/|~(?:$|\/)|\$\{?HOME\}?(?:$|\/)|\.\.(?:$|\/))/.test(s) || s.includes("/../") || s.endsWith("/..");
|
|
2694
|
+
}
|
|
2695
|
+
var bare = (v) => v.replace(/^@/, "");
|
|
2696
|
+
function scanSimple(tokens, roots, cwd) {
|
|
2697
|
+
const scoped = { ...roots, cwd };
|
|
2698
|
+
const check = (raw, need) => withinRoots(bare(raw), scoped, need) ? null : { kind: "escape", path: bare(raw), need };
|
|
2699
|
+
const argv0 = tokens.find((t) => !t.operator && !t.value.includes("="))?.value ?? "";
|
|
2700
|
+
const cmdName = argv0.split("/").pop() ?? argv0;
|
|
2701
|
+
if (SHELL_ARGV0.has(cmdName)) {
|
|
2702
|
+
for (const t of tokens) {
|
|
2703
|
+
if (t.operator || t.value === argv0) continue;
|
|
2704
|
+
if (t.quoted || t.value.includes(" ")) {
|
|
2705
|
+
const inner = scanCommand(t.value, roots, cwd);
|
|
2706
|
+
if (inner.kind !== "ok") return inner;
|
|
2707
|
+
} else if (looksLikePath(t)) {
|
|
2708
|
+
const bad = check(t.value, "read");
|
|
2709
|
+
if (bad) return bad;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
return { kind: "ok" };
|
|
2713
|
+
}
|
|
2714
|
+
if (NO_PATH_ARGV0.has(cmdName)) {
|
|
2715
|
+
return { kind: "ok" };
|
|
2716
|
+
}
|
|
2717
|
+
const writeAll = WRITE_ALL.has(cmdName);
|
|
2718
|
+
const writeLast = WRITE_LAST.has(cmdName);
|
|
2719
|
+
const operands = [];
|
|
2720
|
+
let patternPending = PATTERN_FIRST.has(cmdName);
|
|
2721
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2722
|
+
const t = tokens[i];
|
|
2723
|
+
if (t.operator) continue;
|
|
2724
|
+
if (t.value === argv0 && i === tokens.indexOf(t)) {
|
|
2725
|
+
if (looksLikePath(t)) {
|
|
2726
|
+
const bad = check(t.value, "read");
|
|
2727
|
+
if (bad) return bad;
|
|
2728
|
+
}
|
|
2729
|
+
continue;
|
|
2730
|
+
}
|
|
2731
|
+
if (DIR_FLAGS.has(t.value)) {
|
|
2732
|
+
const operand = tokens[i + 1];
|
|
2733
|
+
if (operand && !operand.operator) {
|
|
2734
|
+
const bad = check(operand.value, "read");
|
|
2735
|
+
if (bad) return bad;
|
|
2736
|
+
i++;
|
|
2737
|
+
}
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
if (PATTERN_FLAGS.has(t.value)) {
|
|
2741
|
+
i++;
|
|
2742
|
+
continue;
|
|
2743
|
+
}
|
|
2744
|
+
if (t.value.startsWith("-")) continue;
|
|
2745
|
+
if (patternPending) {
|
|
2746
|
+
patternPending = false;
|
|
2747
|
+
continue;
|
|
2748
|
+
}
|
|
2749
|
+
if (looksLikePath(t)) operands.push({ token: t, index: i });
|
|
2750
|
+
}
|
|
2751
|
+
if (cmdName === "cd") {
|
|
2752
|
+
const target = tokens.find((t) => !t.operator && t.value !== argv0 && !t.value.startsWith("-"));
|
|
2753
|
+
if (!target) return { kind: "ok" };
|
|
2754
|
+
const bad = check(target.value, "read");
|
|
2755
|
+
if (bad) return bad;
|
|
2756
|
+
return { kind: "cd", to: target.value };
|
|
2757
|
+
}
|
|
2758
|
+
for (let n = 0; n < operands.length; n++) {
|
|
2759
|
+
const entry = operands[n];
|
|
2760
|
+
const isLast = n === operands.length - 1;
|
|
2761
|
+
const need = writeAll || writeLast && isLast ? "write" : "read";
|
|
2762
|
+
const bad = check(entry.token.value, need);
|
|
2763
|
+
if (bad) return bad;
|
|
2764
|
+
}
|
|
2765
|
+
return { kind: "ok" };
|
|
2766
|
+
}
|
|
2767
|
+
function scanCommand(cmd, roots, cwd = roots.cwd) {
|
|
2768
|
+
if (!cmd.trim()) return { kind: "ok" };
|
|
2769
|
+
const lexed = tokenise(cmd);
|
|
2770
|
+
if (!lexed) return { kind: "unparseable", reason: "unbalanced quotes or unterminated substitution" };
|
|
2771
|
+
for (const body of lexed.subshells) {
|
|
2772
|
+
const inner = scanCommand(body, roots, cwd);
|
|
2773
|
+
if (inner.kind !== "ok") return inner;
|
|
2774
|
+
}
|
|
2775
|
+
let current = [];
|
|
2776
|
+
let cwdNow = cwd;
|
|
2777
|
+
const segments = [];
|
|
2778
|
+
for (const t of lexed.tokens) {
|
|
2779
|
+
if (t.operator && [";", "|", "&", "&&", "||", "\n"].includes(t.operator)) {
|
|
2780
|
+
segments.push(current);
|
|
2781
|
+
current = [];
|
|
2782
|
+
continue;
|
|
2783
|
+
}
|
|
2784
|
+
current.push(t);
|
|
2785
|
+
}
|
|
2786
|
+
segments.push(current);
|
|
2787
|
+
for (const seg of segments) {
|
|
2788
|
+
if (seg.length === 0) continue;
|
|
2789
|
+
const plain = [];
|
|
2790
|
+
for (let i = 0; i < seg.length; i++) {
|
|
2791
|
+
const t = seg[i];
|
|
2792
|
+
if (t.operator && REDIRECTS.has(t.operator)) {
|
|
2793
|
+
const target = seg[i + 1];
|
|
2794
|
+
if (target && !target.operator) {
|
|
2795
|
+
const need = t.operator === "<" || t.operator === "<<<" ? "read" : "write";
|
|
2796
|
+
if (!withinRoots(bare(target.value), { ...roots, cwd: cwdNow }, need)) {
|
|
2797
|
+
return { kind: "escape", path: bare(target.value), need };
|
|
2798
|
+
}
|
|
2799
|
+
i++;
|
|
2800
|
+
}
|
|
2801
|
+
continue;
|
|
2802
|
+
}
|
|
2803
|
+
plain.push(t);
|
|
2804
|
+
}
|
|
2805
|
+
const out = scanSimple(plain, roots, cwdNow);
|
|
2806
|
+
if (out.kind === "cd") {
|
|
2807
|
+
cwdNow = expandPath(out.to, cwdNow);
|
|
2808
|
+
continue;
|
|
2809
|
+
}
|
|
2810
|
+
if (out.kind !== "ok") return out;
|
|
2811
|
+
}
|
|
2812
|
+
return { kind: "ok" };
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
// ../../packages/edge/src/builtins.ts
|
|
2262
2816
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
2263
2817
|
"Write",
|
|
2264
2818
|
"Edit",
|
|
@@ -2300,7 +2854,7 @@ function isDangerousRm(cmd) {
|
|
|
2300
2854
|
}
|
|
2301
2855
|
function currentBranch(worktreePath) {
|
|
2302
2856
|
try {
|
|
2303
|
-
return
|
|
2857
|
+
return execFileSync4("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreePath }).toString().trim();
|
|
2304
2858
|
} catch {
|
|
2305
2859
|
return "";
|
|
2306
2860
|
}
|
|
@@ -2319,9 +2873,54 @@ function commandOf(input) {
|
|
|
2319
2873
|
}
|
|
2320
2874
|
return "";
|
|
2321
2875
|
}
|
|
2876
|
+
var PATH_FIELDS = ["file_path", "notebook_path", "path", "filePath", "cwd", "directory"];
|
|
2877
|
+
var PATH_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit", "apply_patch"]);
|
|
2878
|
+
function pathsIn(input) {
|
|
2879
|
+
if (!input || typeof input !== "object") return [];
|
|
2880
|
+
const o = input;
|
|
2881
|
+
const out = [];
|
|
2882
|
+
for (const f of PATH_FIELDS) {
|
|
2883
|
+
if (typeof o[f] === "string" && o[f]) out.push(o[f]);
|
|
2884
|
+
}
|
|
2885
|
+
if (o.changes && typeof o.changes === "object") {
|
|
2886
|
+
out.push(...Object.keys(o.changes));
|
|
2887
|
+
}
|
|
2888
|
+
return out;
|
|
2889
|
+
}
|
|
2890
|
+
function fsConfineRule(roots) {
|
|
2891
|
+
return {
|
|
2892
|
+
name: "fs_confine",
|
|
2893
|
+
evaluate(event) {
|
|
2894
|
+
if (event.kind !== "action") return null;
|
|
2895
|
+
const deny = (path, need2) => ({
|
|
2896
|
+
verdict: "deny",
|
|
2897
|
+
policy: "fs_confine",
|
|
2898
|
+
reason: `${need2 === "write" ? "write to" : "read of"} "${path}" is outside the run's worktree`
|
|
2899
|
+
});
|
|
2900
|
+
if (SHELL_TOOLS.has(event.tool)) {
|
|
2901
|
+
const result = scanCommand(commandOf(event.input), roots);
|
|
2902
|
+
if (result.kind === "escape") return deny(result.path, result.need);
|
|
2903
|
+
if (result.kind === "unparseable") {
|
|
2904
|
+
return {
|
|
2905
|
+
verdict: "deny",
|
|
2906
|
+
policy: "fs_confine",
|
|
2907
|
+
reason: `shell command could not be parsed for path confinement (${result.reason})`
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
return null;
|
|
2911
|
+
}
|
|
2912
|
+
const need = PATH_WRITE_TOOLS.has(event.tool) ? "write" : "read";
|
|
2913
|
+
for (const p of pathsIn(event.input)) {
|
|
2914
|
+
if (!withinRoots(p, roots, need)) return deny(p, need);
|
|
2915
|
+
}
|
|
2916
|
+
return null;
|
|
2917
|
+
}
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2322
2920
|
function buildRules(policies, ctx) {
|
|
2323
2921
|
const rules = [];
|
|
2324
2922
|
let stageToolCalls = 0;
|
|
2923
|
+
if (ctx.fsRoots && process.env.DAHRK_FS_CONFINE !== "0") rules.push(fsConfineRule(ctx.fsRoots));
|
|
2325
2924
|
for (const p of policies) {
|
|
2326
2925
|
if ("read_only" in p && p.read_only) {
|
|
2327
2926
|
rules.push({
|
|
@@ -2436,22 +3035,23 @@ async function startMcpGateway(opts) {
|
|
|
2436
3035
|
if (upstream.body) await pipeline(Readable.fromWeb(upstream.body), res);
|
|
2437
3036
|
else res.end();
|
|
2438
3037
|
};
|
|
2439
|
-
await new Promise((
|
|
3038
|
+
await new Promise((resolve3) => server.listen(0, "127.0.0.1", resolve3));
|
|
2440
3039
|
const { port } = server.address();
|
|
2441
3040
|
return {
|
|
2442
3041
|
baseUrl: `http://127.0.0.1:${port}`,
|
|
2443
|
-
stop: () => new Promise((
|
|
3042
|
+
stop: () => new Promise((resolve3) => server.close(() => resolve3()))
|
|
2444
3043
|
};
|
|
2445
3044
|
}
|
|
2446
3045
|
|
|
2447
3046
|
// ../../packages/edge/src/stage-runner.ts
|
|
2448
3047
|
var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2449
3048
|
var PREVIEW = 500;
|
|
3049
|
+
var RESULT = 16e3;
|
|
2450
3050
|
var putBytes = async (url, body, contentType) => {
|
|
2451
3051
|
await fetch(url, { method: "PUT", headers: { "content-type": contentType }, body: new Uint8Array(body) });
|
|
2452
3052
|
};
|
|
2453
3053
|
function previewOf(event) {
|
|
2454
|
-
const clip = (v) => (typeof v === "string" ? v : JSON.stringify(v) ?? "").slice(0,
|
|
3054
|
+
const clip = (v, max = PREVIEW) => (typeof v === "string" ? v : JSON.stringify(v) ?? "").slice(0, max);
|
|
2455
3055
|
switch (event.type) {
|
|
2456
3056
|
case "response":
|
|
2457
3057
|
return event.text !== void 0 ? { text: event.text } : {};
|
|
@@ -2460,9 +3060,16 @@ function previewOf(event) {
|
|
|
2460
3060
|
case "thought":
|
|
2461
3061
|
return event.text !== void 0 ? { text: clip(event.text) } : {};
|
|
2462
3062
|
case "action":
|
|
2463
|
-
return {
|
|
3063
|
+
return {
|
|
3064
|
+
tool: event.tool,
|
|
3065
|
+
toolUseId: event.toolUseId,
|
|
3066
|
+
...event.input !== void 0 ? { text: clip(event.input) } : {}
|
|
3067
|
+
};
|
|
2464
3068
|
case "observation":
|
|
2465
|
-
return
|
|
3069
|
+
return {
|
|
3070
|
+
toolUseId: event.toolUseId,
|
|
3071
|
+
...event.output !== void 0 ? { text: clip(event.output, RESULT) } : {}
|
|
3072
|
+
};
|
|
2466
3073
|
case "error":
|
|
2467
3074
|
return { text: clip(event.message) };
|
|
2468
3075
|
default:
|
|
@@ -2475,7 +3082,7 @@ var attemptOf = (jobId) => {
|
|
|
2475
3082
|
};
|
|
2476
3083
|
var digest = (value) => `sha256:${createHash3("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16)}`;
|
|
2477
3084
|
function writeScratchState(ref, job, attempt, status) {
|
|
2478
|
-
const statePath =
|
|
3085
|
+
const statePath = join10(ref.scratchPath, "state.json");
|
|
2479
3086
|
let state;
|
|
2480
3087
|
try {
|
|
2481
3088
|
state = JSON.parse(readFileSync4(statePath, "utf8"));
|
|
@@ -2490,17 +3097,17 @@ function writeIssueContext(ref, issueContext) {
|
|
|
2490
3097
|
if (issueContext === void 0) return;
|
|
2491
3098
|
try {
|
|
2492
3099
|
mkdirSync6(ref.scratchPath, { recursive: true });
|
|
2493
|
-
writeFileSync5(
|
|
3100
|
+
writeFileSync5(join10(ref.scratchPath, "issue.md"), issueContext);
|
|
2494
3101
|
} catch {
|
|
2495
3102
|
}
|
|
2496
3103
|
}
|
|
2497
3104
|
function writeAttachedDocuments(ref, docs) {
|
|
2498
3105
|
if (!docs || docs.length === 0) return;
|
|
2499
3106
|
try {
|
|
2500
|
-
const dir =
|
|
3107
|
+
const dir = join10(ref.scratchPath, "docs");
|
|
2501
3108
|
mkdirSync6(dir, { recursive: true });
|
|
2502
3109
|
for (const doc of docs) {
|
|
2503
|
-
writeFileSync5(
|
|
3110
|
+
writeFileSync5(join10(dir, `${attachedDocBasename2(doc)}.md`), doc.content);
|
|
2504
3111
|
}
|
|
2505
3112
|
} catch {
|
|
2506
3113
|
}
|
|
@@ -2519,7 +3126,7 @@ function writeGuidance(ref, guidance) {
|
|
|
2519
3126
|
if (!guidance || guidance.length === 0) return;
|
|
2520
3127
|
try {
|
|
2521
3128
|
mkdirSync6(ref.scratchPath, { recursive: true });
|
|
2522
|
-
writeFileSync5(
|
|
3129
|
+
writeFileSync5(join10(ref.scratchPath, "guidance.md"), renderGuidanceMarkdown(guidance));
|
|
2523
3130
|
} catch {
|
|
2524
3131
|
}
|
|
2525
3132
|
}
|
|
@@ -2529,11 +3136,11 @@ function capContent(raw) {
|
|
|
2529
3136
|
return raw.length > ARTIFACT_CAP_BYTES ? raw.slice(0, ARTIFACT_CAP_BYTES) : raw;
|
|
2530
3137
|
}
|
|
2531
3138
|
function resolveWorktreeRelativePath(ref, relPath) {
|
|
2532
|
-
if (
|
|
2533
|
-
const root =
|
|
2534
|
-
const target =
|
|
2535
|
-
const fromRoot =
|
|
2536
|
-
if (fromRoot === "" || fromRoot === ".." || fromRoot.startsWith(`..${
|
|
3139
|
+
if (isAbsolute3(relPath)) return void 0;
|
|
3140
|
+
const root = resolve2(ref.worktreePath);
|
|
3141
|
+
const target = resolve2(root, relPath);
|
|
3142
|
+
const fromRoot = relative3(root, target);
|
|
3143
|
+
if (fromRoot === "" || fromRoot === ".." || fromRoot.startsWith(`..${sep3}`) || isAbsolute3(fromRoot)) {
|
|
2537
3144
|
return void 0;
|
|
2538
3145
|
}
|
|
2539
3146
|
return target;
|
|
@@ -2553,13 +3160,13 @@ function readEmittedArtifact(ref, relPath) {
|
|
|
2553
3160
|
}
|
|
2554
3161
|
function scanScratchOutput(ref, preferRel) {
|
|
2555
3162
|
try {
|
|
2556
|
-
const names = readdirSync3(
|
|
3163
|
+
const names = readdirSync3(join10(ref.worktreePath, SCRATCH_OUTPUT_DIR)).filter(
|
|
2557
3164
|
(n) => n.toLowerCase().endsWith(".md")
|
|
2558
3165
|
);
|
|
2559
3166
|
if (names.length === 0) return void 0;
|
|
2560
3167
|
const preferBase = preferRel?.split("/").pop();
|
|
2561
3168
|
const pick = preferBase && names.includes(preferBase) ? preferBase : names[0];
|
|
2562
|
-
const raw = readFileSync4(
|
|
3169
|
+
const raw = readFileSync4(join10(ref.worktreePath, SCRATCH_OUTPUT_DIR, pick), "utf8");
|
|
2563
3170
|
if (raw.trim().length === 0) return void 0;
|
|
2564
3171
|
return { path: `${SCRATCH_OUTPUT_DIR}/${pick}`, content: capContent(raw) };
|
|
2565
3172
|
} catch {
|
|
@@ -2569,7 +3176,7 @@ function scanScratchOutput(ref, preferRel) {
|
|
|
2569
3176
|
function scanChangedMarkdown(ref) {
|
|
2570
3177
|
const git = (args) => {
|
|
2571
3178
|
try {
|
|
2572
|
-
return
|
|
3179
|
+
return execFileSync5("git", ["-C", ref.worktreePath, ...args], { encoding: "utf8" }).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
2573
3180
|
} catch {
|
|
2574
3181
|
return [];
|
|
2575
3182
|
}
|
|
@@ -2579,7 +3186,7 @@ function scanChangedMarkdown(ref) {
|
|
|
2579
3186
|
);
|
|
2580
3187
|
for (const rel of rels) {
|
|
2581
3188
|
try {
|
|
2582
|
-
const raw = readFileSync4(
|
|
3189
|
+
const raw = readFileSync4(join10(ref.worktreePath, rel), "utf8");
|
|
2583
3190
|
if (raw.trim().length > 0) return { path: rel, content: capContent(raw) };
|
|
2584
3191
|
} catch {
|
|
2585
3192
|
}
|
|
@@ -2702,9 +3309,9 @@ function createStageRunner(deps) {
|
|
|
2702
3309
|
...job.workspaceRef.credentialToken ? { credentialToken: job.workspaceRef.credentialToken } : {}
|
|
2703
3310
|
});
|
|
2704
3311
|
} else {
|
|
2705
|
-
const base = deps.scratchRoot ??
|
|
2706
|
-
const worktreePath =
|
|
2707
|
-
const scratchPath =
|
|
3312
|
+
const base = deps.scratchRoot ?? join10(tmpdir4(), "dahrk", "scratch");
|
|
3313
|
+
const worktreePath = join10(base, runId);
|
|
3314
|
+
const scratchPath = join10(worktreePath, ".skakel", "scratch");
|
|
2708
3315
|
mkdirSync6(scratchPath, { recursive: true });
|
|
2709
3316
|
ref = { repoId: "", gitUrl: "", repo: "", baseBranch: "", worktreePath, scratchPath };
|
|
2710
3317
|
scratchOnly.add(runId);
|
|
@@ -2720,7 +3327,7 @@ function createStageRunner(deps) {
|
|
|
2720
3327
|
const slash = job.agentConfig.emitArtifact.lastIndexOf("/");
|
|
2721
3328
|
if (artifactDir && slash > 0) {
|
|
2722
3329
|
try {
|
|
2723
|
-
mkdirSync6(
|
|
3330
|
+
mkdirSync6(join10(ref.worktreePath, job.agentConfig.emitArtifact.slice(0, slash)), { recursive: true });
|
|
2724
3331
|
} catch {
|
|
2725
3332
|
}
|
|
2726
3333
|
}
|
|
@@ -2748,8 +3355,8 @@ function createStageRunner(deps) {
|
|
|
2748
3355
|
if (!sink) return;
|
|
2749
3356
|
const base = { tenantId: job.tenantId, runId, stageId, attempt };
|
|
2750
3357
|
try {
|
|
2751
|
-
for (const name of readdirSync3(
|
|
2752
|
-
const bytes = readFileSync4(
|
|
3358
|
+
for (const name of readdirSync3(join10(writer.dir, "blobs"))) {
|
|
3359
|
+
const bytes = readFileSync4(join10(writer.dir, "blobs", name));
|
|
2753
3360
|
const { url } = await sink.requestBlobUrl({
|
|
2754
3361
|
...base,
|
|
2755
3362
|
sha256: name,
|
|
@@ -2763,7 +3370,7 @@ function createStageRunner(deps) {
|
|
|
2763
3370
|
}
|
|
2764
3371
|
let archiveKey;
|
|
2765
3372
|
try {
|
|
2766
|
-
const bytes = readFileSync4(
|
|
3373
|
+
const bytes = readFileSync4(join10(writer.dir, "trace.jsonl"));
|
|
2767
3374
|
const sha = createHash3("sha256").update(bytes).digest("hex");
|
|
2768
3375
|
const { key, url } = await sink.requestBlobUrl({
|
|
2769
3376
|
...base,
|
|
@@ -2847,7 +3454,11 @@ function createStageRunner(deps) {
|
|
|
2847
3454
|
const jobRules = buildRules(job.policies ?? [], {
|
|
2848
3455
|
worktreePath: ref.worktreePath,
|
|
2849
3456
|
repoName: job.workspaceRef?.repo ?? "",
|
|
2850
|
-
runToolCalls: counter
|
|
3457
|
+
runToolCalls: counter,
|
|
3458
|
+
// DHK-392: the stage is confined to the run's worktree (plus its scratch dir, the git object
|
|
3459
|
+
// store it depends on, and the toolchain). A node default, not a workflow policy. A run with
|
|
3460
|
+
// no repo still gets a box - just a smaller one, around its scratch dir.
|
|
3461
|
+
fsRoots: computeFsRoots({ worktreePath: ref.worktreePath, scratchPath: ref.scratchPath })
|
|
2851
3462
|
});
|
|
2852
3463
|
const rules = [...jobRules, ...deps.rules];
|
|
2853
3464
|
const entry = evaluatePolicies({ kind: "stage-entry", stageId }, rules);
|
|
@@ -2856,6 +3467,7 @@ function createStageRunner(deps) {
|
|
|
2856
3467
|
return finish("fail", `${stageId}: denied at stage entry (${entry.policy})`, job.sessionId);
|
|
2857
3468
|
}
|
|
2858
3469
|
let denied = false;
|
|
3470
|
+
let escapedUnblocked = false;
|
|
2859
3471
|
const authorisedActions = [];
|
|
2860
3472
|
const runtime = agentConfig.runtime;
|
|
2861
3473
|
const actionKey = (tool3, input) => {
|
|
@@ -2898,6 +3510,7 @@ function createStageRunner(deps) {
|
|
|
2898
3510
|
if (verdict.verdict === "deny") {
|
|
2899
3511
|
streamEvent(writer.append(event));
|
|
2900
3512
|
recordDeny(verdict, event.toolUseId);
|
|
3513
|
+
if (verdict.policy === "fs_confine" && runtime !== "claude-code") escapedUnblocked = true;
|
|
2901
3514
|
return;
|
|
2902
3515
|
}
|
|
2903
3516
|
}
|
|
@@ -2960,10 +3573,16 @@ function createStageRunner(deps) {
|
|
|
2960
3573
|
if (killTimer) clearTimeout(killTimer);
|
|
2961
3574
|
}
|
|
2962
3575
|
let status = timedOut ? "timeout" : result.status;
|
|
3576
|
+
if (status === "ok" && escapedUnblocked) {
|
|
3577
|
+
status = "fail";
|
|
3578
|
+
const msg = `stage reached outside the run's worktree and the ${runtime} runtime could not block it before it ran`;
|
|
3579
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime, kind: "fs-confine-escape", message: msg });
|
|
3580
|
+
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: msg });
|
|
3581
|
+
}
|
|
2963
3582
|
if (status === "ok" && job.hooks && job.hooks.length > 0) {
|
|
2964
3583
|
for (const cmd of job.hooks) {
|
|
2965
3584
|
try {
|
|
2966
|
-
|
|
3585
|
+
execFileSync5("sh", ["-c", cmd], { cwd: ref.worktreePath, stdio: ["pipe", "pipe", "pipe"] });
|
|
2967
3586
|
} catch (e) {
|
|
2968
3587
|
status = "fail";
|
|
2969
3588
|
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime, kind: "hook-failed", message: `hook "${cmd}" failed: ${e.message}` });
|
|
@@ -3125,9 +3744,20 @@ function createStageRunner(deps) {
|
|
|
3125
3744
|
|
|
3126
3745
|
// ../../packages/edge/src/ws-client.ts
|
|
3127
3746
|
var ENROLMENT_REJECTED_EXIT_CODE = 78;
|
|
3747
|
+
function classifyError(e) {
|
|
3748
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3749
|
+
if (/\bgit\b|worktree|clone|fetch|branch|checkout|remote/i.test(msg)) return "git";
|
|
3750
|
+
if (/policy|denied|not permitted|forbidden/i.test(msg)) return "policy";
|
|
3751
|
+
if (/hub|websocket|socket|awakeable/i.test(msg)) return "hub";
|
|
3752
|
+
if (/runtime|claude|codex|\bpi\b|model|sdk|api key/i.test(msg)) return "runtime";
|
|
3753
|
+
return "internal";
|
|
3754
|
+
}
|
|
3128
3755
|
async function startEdgeNode(opts) {
|
|
3129
3756
|
const log = opts.logger ?? createNodeLogger({ level: levelFromEnv(process.env) });
|
|
3130
3757
|
const rules = opts.denyTool ? [denyToolRule(opts.denyTool)] : [];
|
|
3758
|
+
const counters = new HealthCounters();
|
|
3759
|
+
const shipper = opts.shipper;
|
|
3760
|
+
const telemetryCeiling = ceilingFromEnv(process.env);
|
|
3131
3761
|
const gitLog = log.child({ component: "git" });
|
|
3132
3762
|
const gitLogger = {
|
|
3133
3763
|
info: (msg) => gitLog.debug(msg),
|
|
@@ -3150,6 +3780,12 @@ async function startEdgeNode(opts) {
|
|
|
3150
3780
|
const send = (msg) => {
|
|
3151
3781
|
if (ws && ws.readyState === WebSocket.OPEN) ws.send(encode(msg));
|
|
3152
3782
|
};
|
|
3783
|
+
shipper?.attach((records) => {
|
|
3784
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
|
|
3785
|
+
ws.send(encode({ type: "node-log", records }));
|
|
3786
|
+
return true;
|
|
3787
|
+
});
|
|
3788
|
+
shipper?.start();
|
|
3153
3789
|
const MISSED_PONGS_BEFORE_DEAD = 3;
|
|
3154
3790
|
const startHeartbeat = (sock, intervalMs) => {
|
|
3155
3791
|
if (heartbeat) clearInterval(heartbeat);
|
|
@@ -3161,7 +3797,18 @@ async function startEdgeNode(opts) {
|
|
|
3161
3797
|
sock.terminate();
|
|
3162
3798
|
return;
|
|
3163
3799
|
}
|
|
3164
|
-
|
|
3800
|
+
const health = shipper?.current().health ?? telemetryCeiling.health;
|
|
3801
|
+
send(
|
|
3802
|
+
health ? {
|
|
3803
|
+
type: "heartbeat",
|
|
3804
|
+
health: collectHealth({
|
|
3805
|
+
counters,
|
|
3806
|
+
clientVersion: opts.clientVersion ?? "0.0.0",
|
|
3807
|
+
runtimes: opts.runtimes,
|
|
3808
|
+
worktreesDir: gitService.worktreesDir
|
|
3809
|
+
})
|
|
3810
|
+
} : { type: "heartbeat" }
|
|
3811
|
+
);
|
|
3165
3812
|
if (sock.readyState === WebSocket.OPEN) sock.ping();
|
|
3166
3813
|
}, intervalMs);
|
|
3167
3814
|
};
|
|
@@ -3180,12 +3827,12 @@ async function startEdgeNode(opts) {
|
|
|
3180
3827
|
const trace = {
|
|
3181
3828
|
event: (frame) => send({ type: "trace-event", ...frame }),
|
|
3182
3829
|
finalised: (frame) => send({ type: "trace-finalised", ...frame }),
|
|
3183
|
-
requestBlobUrl: (req) => new Promise((
|
|
3830
|
+
requestBlobUrl: (req) => new Promise((resolve3) => {
|
|
3184
3831
|
const reqId = `${req.runId}:${req.stageId}:${req.attempt}:${blobReqCounter++}`;
|
|
3185
|
-
pendingBlob.set(reqId,
|
|
3832
|
+
pendingBlob.set(reqId, resolve3);
|
|
3186
3833
|
send({ type: "blob-put-request", reqId, ...req });
|
|
3187
3834
|
setTimeout(() => {
|
|
3188
|
-
if (pendingBlob.delete(reqId))
|
|
3835
|
+
if (pendingBlob.delete(reqId)) resolve3({ key: "" });
|
|
3189
3836
|
}, 3e4).unref?.();
|
|
3190
3837
|
})
|
|
3191
3838
|
};
|
|
@@ -3206,6 +3853,7 @@ async function startEdgeNode(opts) {
|
|
|
3206
3853
|
const stageRunner = createStageRunner(stageDeps);
|
|
3207
3854
|
stageRunnerRef = stageRunner;
|
|
3208
3855
|
void stageRunner.reapWorktrees().then((r) => {
|
|
3856
|
+
counters.worktreeCount = Math.max(r.scanned - r.reaped.length, 0);
|
|
3209
3857
|
if (r.reaped.length) {
|
|
3210
3858
|
log.info(
|
|
3211
3859
|
{ reaped: r.reaped, scanned: r.scanned, skipped: r.skipped },
|
|
@@ -3224,8 +3872,15 @@ async function startEdgeNode(opts) {
|
|
|
3224
3872
|
if (opts.heartbeatMs === void 0 && msg.heartbeatMs > 0 && ws) {
|
|
3225
3873
|
startHeartbeat(ws, msg.heartbeatMs);
|
|
3226
3874
|
}
|
|
3875
|
+
if (msg.telemetry && shipper) shipper.setPolicy(msg.telemetry);
|
|
3227
3876
|
log.info(
|
|
3228
|
-
{
|
|
3877
|
+
{
|
|
3878
|
+
name: msg.name,
|
|
3879
|
+
tenantId: msg.tenantId,
|
|
3880
|
+
credentialMode: msg.credentialMode,
|
|
3881
|
+
heartbeatMs: msg.heartbeatMs,
|
|
3882
|
+
...shipper ? { telemetry: shipper.current() } : {}
|
|
3883
|
+
},
|
|
3229
3884
|
`EDGE_WELCOMED:${msg.name} tenant=${msg.tenantId} credentialMode=${msg.credentialMode}`
|
|
3230
3885
|
);
|
|
3231
3886
|
try {
|
|
@@ -3240,13 +3895,18 @@ async function startEdgeNode(opts) {
|
|
|
3240
3895
|
return;
|
|
3241
3896
|
}
|
|
3242
3897
|
if (msg.type === "blob-put-url") {
|
|
3243
|
-
const
|
|
3244
|
-
if (
|
|
3898
|
+
const resolve3 = pendingBlob.get(msg.reqId);
|
|
3899
|
+
if (resolve3) {
|
|
3245
3900
|
pendingBlob.delete(msg.reqId);
|
|
3246
|
-
|
|
3901
|
+
resolve3({ key: msg.key, ...msg.url ? { url: msg.url } : {} });
|
|
3247
3902
|
}
|
|
3248
3903
|
return;
|
|
3249
3904
|
}
|
|
3905
|
+
if (msg.type === "policy") {
|
|
3906
|
+
shipper?.setPolicy(msg.telemetry);
|
|
3907
|
+
log.info({ telemetry: shipper?.current() ?? msg.telemetry }, `EDGE_POLICY:logs=${shipper?.current().logs ?? msg.telemetry.logs}`);
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3250
3910
|
if (msg.type === "cancel") {
|
|
3251
3911
|
log.info({ jobId: msg.jobId }, `JOB_CANCEL:${msg.jobId}`);
|
|
3252
3912
|
stageRunner.cancel(msg.jobId);
|
|
@@ -3319,6 +3979,7 @@ async function startEdgeNode(opts) {
|
|
|
3319
3979
|
return;
|
|
3320
3980
|
}
|
|
3321
3981
|
running.add(job.jobId);
|
|
3982
|
+
counters.activeJobs = running.size;
|
|
3322
3983
|
const startedAt = Date.now();
|
|
3323
3984
|
jobLog.info({}, `JOB_STARTED:${job.stageId} ${job.jobId}`);
|
|
3324
3985
|
try {
|
|
@@ -3338,9 +3999,11 @@ async function startEdgeNode(opts) {
|
|
|
3338
3999
|
};
|
|
3339
4000
|
rememberResult(job.jobId, frame);
|
|
3340
4001
|
send(frame);
|
|
4002
|
+
counters.recordError(classifyError(e));
|
|
3341
4003
|
jobLog.error({ err: e, durationMs: Date.now() - startedAt }, `JOB_ERROR:${job.stageId} ${e.message}`);
|
|
3342
4004
|
} finally {
|
|
3343
4005
|
running.delete(job.jobId);
|
|
4006
|
+
counters.activeJobs = running.size;
|
|
3344
4007
|
}
|
|
3345
4008
|
};
|
|
3346
4009
|
const connect = () => {
|
|
@@ -3348,6 +4011,7 @@ async function startEdgeNode(opts) {
|
|
|
3348
4011
|
ws = sock;
|
|
3349
4012
|
sock.on("open", () => {
|
|
3350
4013
|
connectCount++;
|
|
4014
|
+
counters.connectCount = connectCount;
|
|
3351
4015
|
log.info({ hubUrl: opts.hubUrl, nodeId, connectCount }, "EDGE_CONNECTED");
|
|
3352
4016
|
send({
|
|
3353
4017
|
type: "hello",
|
|
@@ -3403,11 +4067,11 @@ async function startEdgeNode(opts) {
|
|
|
3403
4067
|
{ once: true }
|
|
3404
4068
|
);
|
|
3405
4069
|
connect();
|
|
3406
|
-
await new Promise((
|
|
4070
|
+
await new Promise((resolve3, reject) => {
|
|
3407
4071
|
const t = setInterval(() => {
|
|
3408
4072
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
3409
4073
|
clearInterval(t);
|
|
3410
|
-
|
|
4074
|
+
resolve3();
|
|
3411
4075
|
}
|
|
3412
4076
|
}, 50);
|
|
3413
4077
|
onFatal = (err) => {
|
|
@@ -3425,11 +4089,11 @@ var PROBES = [
|
|
|
3425
4089
|
{ runtime: "pi", cmd: "pi" }
|
|
3426
4090
|
];
|
|
3427
4091
|
function probe(cmd, timeoutMs) {
|
|
3428
|
-
return new Promise((
|
|
4092
|
+
return new Promise((resolve3) => {
|
|
3429
4093
|
execFile(cmd, ["--version"], { timeout: timeoutMs }, (err, stdout) => {
|
|
3430
|
-
if (err) return
|
|
4094
|
+
if (err) return resolve3(void 0);
|
|
3431
4095
|
const line = stdout.split("\n").map((s) => s.trim()).find(Boolean);
|
|
3432
|
-
|
|
4096
|
+
resolve3(line ?? "");
|
|
3433
4097
|
});
|
|
3434
4098
|
});
|
|
3435
4099
|
}
|
|
@@ -3472,7 +4136,7 @@ function probeHub(opts) {
|
|
|
3472
4136
|
clientVersion = "0.0.0",
|
|
3473
4137
|
timeoutMs = 8e3
|
|
3474
4138
|
} = opts;
|
|
3475
|
-
return new Promise((
|
|
4139
|
+
return new Promise((resolve3) => {
|
|
3476
4140
|
let settled = false;
|
|
3477
4141
|
let opened = false;
|
|
3478
4142
|
let ws;
|
|
@@ -3484,7 +4148,7 @@ function probeHub(opts) {
|
|
|
3484
4148
|
ws.terminate();
|
|
3485
4149
|
} catch {
|
|
3486
4150
|
}
|
|
3487
|
-
|
|
4151
|
+
resolve3(result);
|
|
3488
4152
|
};
|
|
3489
4153
|
const timer = setTimeout(
|
|
3490
4154
|
() => done({ ok: false, reason: "timeout", detail: `no welcome within ${timeoutMs}ms` }),
|
|
@@ -3944,8 +4608,8 @@ function usage(bin, command) {
|
|
|
3944
4608
|
}
|
|
3945
4609
|
|
|
3946
4610
|
// src/diagnose.ts
|
|
3947
|
-
import { existsSync as
|
|
3948
|
-
import { join as
|
|
4611
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
4612
|
+
import { join as join11 } from "path";
|
|
3949
4613
|
var BUNDLE_LOG_LINES = 2e3;
|
|
3950
4614
|
function tailJsonl(raw, n) {
|
|
3951
4615
|
const records = [];
|
|
@@ -3986,7 +4650,7 @@ async function buildBundle(deps) {
|
|
|
3986
4650
|
if (deps.exists(deps.crashDir)) {
|
|
3987
4651
|
for (const name of deps.listDir(deps.crashDir).filter((f) => f.endsWith(".json")).sort()) {
|
|
3988
4652
|
try {
|
|
3989
|
-
crashes.push(JSON.parse(deps.readFile(
|
|
4653
|
+
crashes.push(JSON.parse(deps.readFile(join11(deps.crashDir, name))));
|
|
3990
4654
|
} catch (e) {
|
|
3991
4655
|
warnings.push(`could not read crash record ${name} (${e.message})`);
|
|
3992
4656
|
}
|
|
@@ -4045,17 +4709,17 @@ var defaultDiagnoseDeps = (paths, clientVersion, doctor) => ({
|
|
|
4045
4709
|
...doctor ? { doctor } : {},
|
|
4046
4710
|
readFile: (p) => readFileSync5(p, "utf8"),
|
|
4047
4711
|
listDir: (p) => readdirSync4(p),
|
|
4048
|
-
exists: (p) =>
|
|
4712
|
+
exists: (p) => existsSync7(p),
|
|
4049
4713
|
writeFile: (p, content) => {
|
|
4050
|
-
const dir =
|
|
4051
|
-
if (!
|
|
4714
|
+
const dir = join11(p, "..");
|
|
4715
|
+
if (!existsSync7(dir)) mkdirSync7(dir, { recursive: true });
|
|
4052
4716
|
writeFileSync6(p, content, { mode: 384 });
|
|
4053
4717
|
},
|
|
4054
4718
|
out: (line) => void process.stdout.write(`${line}
|
|
4055
4719
|
`)
|
|
4056
4720
|
});
|
|
4057
4721
|
function defaultBundlePath(cwd, now) {
|
|
4058
|
-
return
|
|
4722
|
+
return join11(cwd, `dahrk-diagnose-${now.toISOString().replace(/[:.]/g, "-")}.json`);
|
|
4059
4723
|
}
|
|
4060
4724
|
|
|
4061
4725
|
// src/doctor.ts
|
|
@@ -4190,8 +4854,8 @@ async function runDoctor(inputs, deps = {}) {
|
|
|
4190
4854
|
}
|
|
4191
4855
|
|
|
4192
4856
|
// src/lock.ts
|
|
4193
|
-
import { existsSync as
|
|
4194
|
-
import { dirname as
|
|
4857
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync6, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
4858
|
+
import { dirname as dirname5 } from "path";
|
|
4195
4859
|
function parseLock(content) {
|
|
4196
4860
|
if (!content) return void 0;
|
|
4197
4861
|
const pid = Number(content.trim());
|
|
@@ -4204,7 +4868,11 @@ function acquireLock(deps) {
|
|
|
4204
4868
|
}
|
|
4205
4869
|
deps.writeFile(deps.file, `${deps.pid}
|
|
4206
4870
|
`);
|
|
4207
|
-
return { ok: true, release: () =>
|
|
4871
|
+
return { ok: true, release: () => releaseLock(deps) };
|
|
4872
|
+
}
|
|
4873
|
+
function releaseLock(deps) {
|
|
4874
|
+
if (parseLock(deps.readFile(deps.file)) !== deps.pid) return;
|
|
4875
|
+
deps.removeFile(deps.file);
|
|
4208
4876
|
}
|
|
4209
4877
|
function isAlive(pid) {
|
|
4210
4878
|
try {
|
|
@@ -4225,7 +4893,7 @@ var defaultLockDeps = (file) => ({
|
|
|
4225
4893
|
}
|
|
4226
4894
|
},
|
|
4227
4895
|
writeFile: (path, content) => {
|
|
4228
|
-
if (!
|
|
4896
|
+
if (!existsSync8(dirname5(path))) mkdirSync8(dirname5(path), { recursive: true, mode: 448 });
|
|
4229
4897
|
writeFileSync7(path, content);
|
|
4230
4898
|
},
|
|
4231
4899
|
removeFile: (path) => rmSync6(path, { force: true }),
|
|
@@ -4234,7 +4902,7 @@ var defaultLockDeps = (file) => ({
|
|
|
4234
4902
|
|
|
4235
4903
|
// src/logs.ts
|
|
4236
4904
|
import { spawn } from "child_process";
|
|
4237
|
-
import { copyFileSync, existsSync as
|
|
4905
|
+
import { copyFileSync, existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync4, truncateSync } from "fs";
|
|
4238
4906
|
var MAX_LOG_BYTES = 10 * 1024 * 1024;
|
|
4239
4907
|
function logsCommand(inputs) {
|
|
4240
4908
|
return [
|
|
@@ -4315,7 +4983,7 @@ async function runStructuredLogs(inputs, deps) {
|
|
|
4315
4983
|
}
|
|
4316
4984
|
function rotateIfLarge(file, maxBytes = MAX_LOG_BYTES) {
|
|
4317
4985
|
try {
|
|
4318
|
-
if (!
|
|
4986
|
+
if (!existsSync9(file) || statSync4(file).size <= maxBytes) return;
|
|
4319
4987
|
copyFileSync(file, `${file}.1`);
|
|
4320
4988
|
truncateSync(file, 0);
|
|
4321
4989
|
} catch {
|
|
@@ -4324,13 +4992,13 @@ function rotateIfLarge(file, maxBytes = MAX_LOG_BYTES) {
|
|
|
4324
4992
|
var defaultLogsDeps = (files, jsonlFile) => ({
|
|
4325
4993
|
files,
|
|
4326
4994
|
jsonlFile,
|
|
4327
|
-
fileExists: (path) =>
|
|
4995
|
+
fileExists: (path) => existsSync9(path),
|
|
4328
4996
|
readFile: (path) => readFileSync7(path, "utf8"),
|
|
4329
|
-
run: (argv) => new Promise((
|
|
4997
|
+
run: (argv) => new Promise((resolve3) => {
|
|
4330
4998
|
const [cmd, ...args] = argv;
|
|
4331
4999
|
const child = spawn(cmd, args, { stdio: "inherit" });
|
|
4332
|
-
child.on("error", () =>
|
|
4333
|
-
child.on("close", (code) =>
|
|
5000
|
+
child.on("error", () => resolve3(1));
|
|
5001
|
+
child.on("close", (code) => resolve3(code ?? 0));
|
|
4334
5002
|
}),
|
|
4335
5003
|
out: (line) => void process.stdout.write(`${line}
|
|
4336
5004
|
`)
|
|
@@ -4338,11 +5006,11 @@ var defaultLogsDeps = (files, jsonlFile) => ({
|
|
|
4338
5006
|
|
|
4339
5007
|
// src/process-safety.ts
|
|
4340
5008
|
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
4341
|
-
import { join as
|
|
5009
|
+
import { join as join12 } from "path";
|
|
4342
5010
|
function writeCrashRecord(dir, record) {
|
|
4343
5011
|
try {
|
|
4344
5012
|
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
4345
|
-
const file =
|
|
5013
|
+
const file = join12(dir, `${record.at.replace(/[:.]/g, "-")}.json`);
|
|
4346
5014
|
writeFileSync8(file, `${JSON.stringify(record, null, 2)}
|
|
4347
5015
|
`, { mode: 384 });
|
|
4348
5016
|
return file;
|
|
@@ -4396,11 +5064,11 @@ function installProcessSafetyNet(opts) {
|
|
|
4396
5064
|
}
|
|
4397
5065
|
|
|
4398
5066
|
// src/preflight.ts
|
|
4399
|
-
import { execFileSync as
|
|
5067
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
4400
5068
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4401
|
-
import { accessSync, constants as fsConstants, existsSync as
|
|
4402
|
-
import { homedir as
|
|
4403
|
-
import { join as
|
|
5069
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync10, readdirSync as readdirSync5, statfsSync as statfsSync2 } from "fs";
|
|
5070
|
+
import { homedir as homedir4 } from "os";
|
|
5071
|
+
import { join as join13 } from "path";
|
|
4404
5072
|
var REPORT_BASE_URL = "https://app.dahrk.ai/r";
|
|
4405
5073
|
var LOW_DISK_BYTES = 512 * 1024 * 1024;
|
|
4406
5074
|
var PREFLIGHT_STAGES = [
|
|
@@ -4529,7 +5197,7 @@ var defaultDeps2 = () => ({
|
|
|
4529
5197
|
});
|
|
4530
5198
|
function commandPresent(cmd) {
|
|
4531
5199
|
try {
|
|
4532
|
-
|
|
5200
|
+
execFileSync6("sh", ["-c", `command -v ${cmd}`], { stdio: "ignore" });
|
|
4533
5201
|
return true;
|
|
4534
5202
|
} catch {
|
|
4535
5203
|
return false;
|
|
@@ -4537,12 +5205,12 @@ function commandPresent(cmd) {
|
|
|
4537
5205
|
}
|
|
4538
5206
|
function sshKeyPresent() {
|
|
4539
5207
|
try {
|
|
4540
|
-
const dir =
|
|
4541
|
-
if (
|
|
5208
|
+
const dir = join13(homedir4(), ".ssh");
|
|
5209
|
+
if (existsSync10(dir) && readdirSync5(dir).some((f) => f.endsWith(".pub"))) return true;
|
|
4542
5210
|
} catch {
|
|
4543
5211
|
}
|
|
4544
5212
|
try {
|
|
4545
|
-
|
|
5213
|
+
execFileSync6("ssh-add", ["-l"], { stdio: "ignore" });
|
|
4546
5214
|
return true;
|
|
4547
5215
|
} catch {
|
|
4548
5216
|
return false;
|
|
@@ -4557,12 +5225,12 @@ function writable(dir) {
|
|
|
4557
5225
|
}
|
|
4558
5226
|
}
|
|
4559
5227
|
function worktreeRoot(env) {
|
|
4560
|
-
return env.DAHRK_WORKTREES_DIR ??
|
|
5228
|
+
return env.DAHRK_WORKTREES_DIR ?? join13(env.DAHRK_STATE_DIR ?? join13(homedir4(), ".dahrk"), "worktrees");
|
|
4561
5229
|
}
|
|
4562
5230
|
function nearestExisting(dir) {
|
|
4563
5231
|
let cur = dir;
|
|
4564
|
-
while (!
|
|
4565
|
-
const parent =
|
|
5232
|
+
while (!existsSync10(cur)) {
|
|
5233
|
+
const parent = join13(cur, "..");
|
|
4566
5234
|
if (parent === cur) break;
|
|
4567
5235
|
cur = parent;
|
|
4568
5236
|
}
|
|
@@ -4570,14 +5238,14 @@ function nearestExisting(dir) {
|
|
|
4570
5238
|
}
|
|
4571
5239
|
function freeDiskBytes(dir) {
|
|
4572
5240
|
try {
|
|
4573
|
-
const s =
|
|
5241
|
+
const s = statfsSync2(nearestExisting(dir));
|
|
4574
5242
|
return s.bavail * s.bsize;
|
|
4575
5243
|
} catch {
|
|
4576
5244
|
return void 0;
|
|
4577
5245
|
}
|
|
4578
5246
|
}
|
|
4579
5247
|
function probeRepo(repoPath) {
|
|
4580
|
-
const git = (args) =>
|
|
5248
|
+
const git = (args) => execFileSync6("git", ["-C", repoPath, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
4581
5249
|
try {
|
|
4582
5250
|
if (git(["rev-parse", "--is-inside-work-tree"]) !== "true") {
|
|
4583
5251
|
return { path: repoPath, isGitRepo: false, headResolves: false, detail: "not inside a work tree" };
|
|
@@ -4612,49 +5280,49 @@ function gatherHostFacts(repoPath) {
|
|
|
4612
5280
|
}
|
|
4613
5281
|
|
|
4614
5282
|
// src/service.ts
|
|
4615
|
-
import { execFileSync as
|
|
4616
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
4617
|
-
import { homedir as
|
|
4618
|
-
import { join as
|
|
5283
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
5284
|
+
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync9, realpathSync as realpathSync3, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
5285
|
+
import { homedir as homedir6, platform as osPlatform3, userInfo } from "os";
|
|
5286
|
+
import { join as join15 } from "path";
|
|
4619
5287
|
|
|
4620
5288
|
// src/state.ts
|
|
4621
|
-
import { chmodSync, existsSync as
|
|
4622
|
-
import { homedir as
|
|
4623
|
-
import { join as
|
|
5289
|
+
import { chmodSync, existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
5290
|
+
import { homedir as homedir5 } from "os";
|
|
5291
|
+
import { join as join14 } from "path";
|
|
4624
5292
|
var STRING_FIELDS = ["nodeId", "enrolToken", "name", "tenantId", "updateCheckedAt", "updateLatest"];
|
|
4625
5293
|
var isDesired = (v) => v === "running" || v === "stopped";
|
|
4626
5294
|
var FILE_MODE = 384;
|
|
4627
5295
|
var DIR_MODE = 448;
|
|
4628
5296
|
function stateDir(env) {
|
|
4629
|
-
return env.DAHRK_STATE_DIR ??
|
|
5297
|
+
return env.DAHRK_STATE_DIR ?? join14(homedir5(), ".dahrk");
|
|
4630
5298
|
}
|
|
4631
5299
|
function legacyStateDir(env) {
|
|
4632
|
-
return env.DAHRK_STATE_DIR ? void 0 :
|
|
5300
|
+
return env.DAHRK_STATE_DIR ? void 0 : join14(homedir5(), ".skakel");
|
|
4633
5301
|
}
|
|
4634
5302
|
function stateFile(env) {
|
|
4635
|
-
return
|
|
5303
|
+
return join14(stateDir(env), "node.json");
|
|
4636
5304
|
}
|
|
4637
5305
|
function logDir(env) {
|
|
4638
|
-
return
|
|
5306
|
+
return join14(stateDir(env), "logs");
|
|
4639
5307
|
}
|
|
4640
5308
|
function logFiles(env) {
|
|
4641
5309
|
const dir = logDir(env);
|
|
4642
|
-
return { out:
|
|
5310
|
+
return { out: join14(dir, "node.out.log"), err: join14(dir, "node.err.log") };
|
|
4643
5311
|
}
|
|
4644
5312
|
function jsonlLogFile(env) {
|
|
4645
|
-
return
|
|
5313
|
+
return join14(logDir(env), "node.jsonl");
|
|
4646
5314
|
}
|
|
4647
5315
|
function crashDir(env) {
|
|
4648
|
-
return
|
|
5316
|
+
return join14(logDir(env), "crashes");
|
|
4649
5317
|
}
|
|
4650
5318
|
function lockFile(env) {
|
|
4651
|
-
return
|
|
5319
|
+
return join14(stateDir(env), "node.pid");
|
|
4652
5320
|
}
|
|
4653
5321
|
function setDesired(env, desired) {
|
|
4654
5322
|
writeState(env, { desired });
|
|
4655
5323
|
}
|
|
4656
5324
|
function readState(file) {
|
|
4657
|
-
if (!
|
|
5325
|
+
if (!existsSync11(file)) return {};
|
|
4658
5326
|
try {
|
|
4659
5327
|
const parsed = JSON.parse(readFileSync8(file, "utf8"));
|
|
4660
5328
|
const state = {};
|
|
@@ -4755,9 +5423,9 @@ ${envEntries}
|
|
|
4755
5423
|
<key>ThrottleInterval</key>
|
|
4756
5424
|
<integer>10</integer>
|
|
4757
5425
|
<key>StandardOutPath</key>
|
|
4758
|
-
<string>${xmlEscape(
|
|
5426
|
+
<string>${xmlEscape(join15(inputs.logDir, "node.out.log"))}</string>
|
|
4759
5427
|
<key>StandardErrorPath</key>
|
|
4760
|
-
<string>${xmlEscape(
|
|
5428
|
+
<string>${xmlEscape(join15(inputs.logDir, "node.err.log"))}</string>
|
|
4761
5429
|
</dict>
|
|
4762
5430
|
</plist>
|
|
4763
5431
|
`;
|
|
@@ -4780,8 +5448,8 @@ Type=simple
|
|
|
4780
5448
|
ExecStart=${exec}
|
|
4781
5449
|
${envLines}
|
|
4782
5450
|
WorkingDirectory=${inputs.homeDir}
|
|
4783
|
-
StandardOutput=append:${
|
|
4784
|
-
StandardError=append:${
|
|
5451
|
+
StandardOutput=append:${join15(inputs.logDir, "node.out.log")}
|
|
5452
|
+
StandardError=append:${join15(inputs.logDir, "node.err.log")}
|
|
4785
5453
|
Restart=on-failure
|
|
4786
5454
|
RestartSec=3
|
|
4787
5455
|
RestartPreventExitStatus=78
|
|
@@ -4792,7 +5460,7 @@ WantedBy=default.target
|
|
|
4792
5460
|
}
|
|
4793
5461
|
function buildPlan(inputs) {
|
|
4794
5462
|
if (inputs.manager === "launchd") {
|
|
4795
|
-
const filePath2 =
|
|
5463
|
+
const filePath2 = join15(inputs.homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
4796
5464
|
return {
|
|
4797
5465
|
manager: "launchd",
|
|
4798
5466
|
label: LAUNCHD_LABEL,
|
|
@@ -4809,7 +5477,7 @@ function buildPlan(inputs) {
|
|
|
4809
5477
|
logHint: "dahrk logs -f"
|
|
4810
5478
|
};
|
|
4811
5479
|
}
|
|
4812
|
-
const filePath =
|
|
5480
|
+
const filePath = join15(inputs.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
4813
5481
|
const user = userInfo().username;
|
|
4814
5482
|
return {
|
|
4815
5483
|
manager: "systemd",
|
|
@@ -4839,7 +5507,7 @@ function unitIsCurrent(plan, onDisk) {
|
|
|
4839
5507
|
return onDisk === plan.content;
|
|
4840
5508
|
}
|
|
4841
5509
|
function unitPath(manager, homeDir) {
|
|
4842
|
-
return manager === "launchd" ?
|
|
5510
|
+
return manager === "launchd" ? join15(homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`) : join15(homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
4843
5511
|
}
|
|
4844
5512
|
function statusCommand(manager) {
|
|
4845
5513
|
return manager === "launchd" ? ["launchctl", "list", LAUNCHD_LABEL] : ["systemctl", "--user", "show", SYSTEMD_UNIT, "-p", "ActiveState", "-p", "MainPID"];
|
|
@@ -4975,6 +5643,12 @@ async function runNodeStart(inputs, deps = {}) {
|
|
|
4975
5643
|
d.out(" stop: dahrk stop");
|
|
4976
5644
|
return { kind: "running", code: 0 };
|
|
4977
5645
|
}
|
|
5646
|
+
var STOP_FOREIGN_NODE = 3;
|
|
5647
|
+
function foreignNodePid(lock, servicePid, isAlive2) {
|
|
5648
|
+
const held = parseLock(lock);
|
|
5649
|
+
if (held === void 0 || held === servicePid) return void 0;
|
|
5650
|
+
return isAlive2(held) ? held : void 0;
|
|
5651
|
+
}
|
|
4978
5652
|
async function runNodeStop(deps = {}) {
|
|
4979
5653
|
const d = { ...defaultDeps3(), ...deps };
|
|
4980
5654
|
const manager = detectManager(d.platform);
|
|
@@ -4990,11 +5664,22 @@ async function runNodeStop(deps = {}) {
|
|
|
4990
5664
|
if (!d.fileExists(plan.filePath)) {
|
|
4991
5665
|
d.out("No service installed, so there is nothing to stop.");
|
|
4992
5666
|
d.out("If you are running a node in a terminal (`dahrk start --foreground`), stop it with Ctrl-C.");
|
|
4993
|
-
return 0;
|
|
5667
|
+
return reportForeignNode(d, void 0) ?? 0;
|
|
4994
5668
|
}
|
|
5669
|
+
const servicePid = parseServiceStatus(manager, true, d.capture(statusCommand(manager))).pid;
|
|
4995
5670
|
runCommands(plan.stopCommands, d);
|
|
4996
5671
|
d.out("Node stopped. It will stay stopped across reboots until you run `dahrk start`.");
|
|
4997
|
-
return 0;
|
|
5672
|
+
return reportForeignNode(d, servicePid) ?? 0;
|
|
5673
|
+
}
|
|
5674
|
+
function reportForeignNode(d, servicePid) {
|
|
5675
|
+
const pid = foreignNodePid(d.readFile(d.lockFile), servicePid, d.isAlive);
|
|
5676
|
+
if (pid === void 0) return void 0;
|
|
5677
|
+
d.out("");
|
|
5678
|
+
d.out(`WARNING: a node is STILL RUNNING on this host (pid ${pid}). \`dahrk stop\` cannot stop it.`);
|
|
5679
|
+
d.out("Something else is supervising it: pm2, a container, or `dahrk start --foreground` in a terminal.");
|
|
5680
|
+
d.out("It is not a stray copy - it holds this node's identity, so it is still connected to the hub and");
|
|
5681
|
+
d.out("still taking Jobs. Stop it where it was started (e.g. `pm2 delete dahrk-node`), or kill the pid.");
|
|
5682
|
+
return STOP_FOREIGN_NODE;
|
|
4998
5683
|
}
|
|
4999
5684
|
async function runServiceUninstall(deps = {}) {
|
|
5000
5685
|
const d = { ...defaultDeps3(), ...deps };
|
|
@@ -5031,7 +5716,7 @@ function dirOf(path) {
|
|
|
5031
5716
|
function resolveScriptPath() {
|
|
5032
5717
|
const argv1 = process.argv[1] ?? "";
|
|
5033
5718
|
try {
|
|
5034
|
-
return
|
|
5719
|
+
return realpathSync3(argv1);
|
|
5035
5720
|
} catch {
|
|
5036
5721
|
return argv1;
|
|
5037
5722
|
}
|
|
@@ -5045,19 +5730,21 @@ function stableNodeBin(execPath, realpath) {
|
|
|
5045
5730
|
}
|
|
5046
5731
|
var realpathOrUndefined = (p) => {
|
|
5047
5732
|
try {
|
|
5048
|
-
return
|
|
5733
|
+
return realpathSync3(p);
|
|
5049
5734
|
} catch {
|
|
5050
5735
|
return void 0;
|
|
5051
5736
|
}
|
|
5052
5737
|
};
|
|
5053
5738
|
var defaultDeps3 = () => ({
|
|
5054
5739
|
platform: osPlatform3(),
|
|
5055
|
-
homeDir:
|
|
5740
|
+
homeDir: homedir6(),
|
|
5056
5741
|
// Not `process.execPath` raw: that is the versioned Homebrew Cellar path, which the next
|
|
5057
5742
|
// `brew upgrade node` deletes out from under the unit. See `stableNodeBin`.
|
|
5058
5743
|
nodeBin: stableNodeBin(process.execPath, realpathOrUndefined),
|
|
5059
5744
|
scriptPath: resolveScriptPath(),
|
|
5060
5745
|
logDir: logDir(process.env),
|
|
5746
|
+
lockFile: lockFile(process.env),
|
|
5747
|
+
isAlive,
|
|
5061
5748
|
// Snapshot the operator's PATH at install time so the daemon finds git + the runtime CLIs (Homebrew /
|
|
5062
5749
|
// npm-global bins) that a supervisor's minimal PATH would otherwise hide.
|
|
5063
5750
|
pathEnv: process.env.PATH,
|
|
@@ -5077,11 +5764,11 @@ var defaultDeps3 = () => ({
|
|
|
5077
5764
|
}
|
|
5078
5765
|
},
|
|
5079
5766
|
removeFile: (path) => rmSync7(path, { force: true }),
|
|
5080
|
-
fileExists: (path) =>
|
|
5767
|
+
fileExists: (path) => existsSync12(path),
|
|
5081
5768
|
run: (argv) => {
|
|
5082
5769
|
const [cmd, ...args] = argv;
|
|
5083
5770
|
try {
|
|
5084
|
-
|
|
5771
|
+
execFileSync7(cmd, args, { stdio: "inherit" });
|
|
5085
5772
|
return 0;
|
|
5086
5773
|
} catch (e) {
|
|
5087
5774
|
const status = e.status;
|
|
@@ -5091,7 +5778,7 @@ var defaultDeps3 = () => ({
|
|
|
5091
5778
|
capture: (argv) => {
|
|
5092
5779
|
const [cmd, ...args] = argv;
|
|
5093
5780
|
try {
|
|
5094
|
-
const stdout =
|
|
5781
|
+
const stdout = execFileSync7(cmd, args, {
|
|
5095
5782
|
encoding: "utf8",
|
|
5096
5783
|
stdio: ["ignore", "pipe", "ignore"]
|
|
5097
5784
|
});
|
|
@@ -5106,8 +5793,8 @@ var defaultDeps3 = () => ({
|
|
|
5106
5793
|
});
|
|
5107
5794
|
|
|
5108
5795
|
// src/update.ts
|
|
5109
|
-
import { execFileSync as
|
|
5110
|
-
import { realpathSync as
|
|
5796
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
5797
|
+
import { realpathSync as realpathSync4 } from "fs";
|
|
5111
5798
|
var LATEST_URL = "https://registry.npmjs.org/dahrk-node/latest";
|
|
5112
5799
|
var CHANNEL_COMMANDS = {
|
|
5113
5800
|
npm: "npm install -g dahrk-node@latest",
|
|
@@ -5128,7 +5815,7 @@ function detectChannel(binPath) {
|
|
|
5128
5815
|
if (!binPath) return "unknown";
|
|
5129
5816
|
let resolved = binPath;
|
|
5130
5817
|
try {
|
|
5131
|
-
resolved =
|
|
5818
|
+
resolved = realpathSync4(binPath);
|
|
5132
5819
|
} catch {
|
|
5133
5820
|
}
|
|
5134
5821
|
if (/[\\/]node_modules[\\/]dahrk-node[\\/]/.test(resolved)) return "npm";
|
|
@@ -5216,7 +5903,7 @@ async function fetchLatestVersion(signal) {
|
|
|
5216
5903
|
function spawnUpgrade(argv) {
|
|
5217
5904
|
const [cmd, ...args] = argv;
|
|
5218
5905
|
try {
|
|
5219
|
-
|
|
5906
|
+
execFileSync8(cmd, args, { stdio: "inherit" });
|
|
5220
5907
|
return 0;
|
|
5221
5908
|
} catch (e) {
|
|
5222
5909
|
const status = e.status;
|
|
@@ -5358,7 +6045,7 @@ async function runStatus(inputs, deps) {
|
|
|
5358
6045
|
}
|
|
5359
6046
|
|
|
5360
6047
|
// src/main.ts
|
|
5361
|
-
var CLIENT_VERSION = "0.1.
|
|
6048
|
+
var CLIENT_VERSION = "0.1.11";
|
|
5362
6049
|
var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
|
|
5363
6050
|
var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
5364
6051
|
var RUNTIMES = ["claude-code", "codex", "pi"];
|
|
@@ -5370,7 +6057,7 @@ function resolveNodeId(env, opts = {}) {
|
|
|
5370
6057
|
if (existing) return existing;
|
|
5371
6058
|
const legacy = legacyStateDir(env);
|
|
5372
6059
|
if (legacy) {
|
|
5373
|
-
const legacyId = readState(
|
|
6060
|
+
const legacyId = readState(join16(legacy, "node.json")).nodeId;
|
|
5374
6061
|
if (legacyId) return legacyId;
|
|
5375
6062
|
}
|
|
5376
6063
|
const nodeId = randomUUID3();
|
|
@@ -5472,7 +6159,8 @@ async function startForeground(env, flags) {
|
|
|
5472
6159
|
rotateIfLarge(files.out);
|
|
5473
6160
|
rotateIfLarge(files.err);
|
|
5474
6161
|
const nodeId = resolveNodeId(env, { ephemeral: flags.ephemeral });
|
|
5475
|
-
const
|
|
6162
|
+
const shipper = new LogShipper({ ceiling: ceilingFromEnv(env) });
|
|
6163
|
+
const logger = createNodeLoggerFromEnv(env, logDir(env), { nodeId, clientVersion: CLIENT_VERSION }, shipperStream(shipper));
|
|
5476
6164
|
installProcessSafetyNet({
|
|
5477
6165
|
logger,
|
|
5478
6166
|
crashDir: crashDir(env),
|
|
@@ -5494,6 +6182,7 @@ async function startForeground(env, flags) {
|
|
|
5494
6182
|
await startEdgeNode({
|
|
5495
6183
|
...buildEdgeOptions(env, resolved),
|
|
5496
6184
|
logger,
|
|
6185
|
+
shipper,
|
|
5497
6186
|
...persist ? {
|
|
5498
6187
|
onEnrolled: (welcome) => persistEnrolment(env, { token, name: welcome.name, tenantId: welcome.tenantId })
|
|
5499
6188
|
} : {}
|
|
@@ -5502,7 +6191,7 @@ async function startForeground(env, flags) {
|
|
|
5502
6191
|
}
|
|
5503
6192
|
async function stop(env) {
|
|
5504
6193
|
const code = await runNodeStop();
|
|
5505
|
-
if (code === 0) setDesired(env, "stopped");
|
|
6194
|
+
if (code === 0 || code === STOP_FOREIGN_NODE) setDesired(env, "stopped");
|
|
5506
6195
|
return code;
|
|
5507
6196
|
}
|
|
5508
6197
|
function updateCheckDeps(env) {
|
|
@@ -5549,15 +6238,15 @@ function scheduleUpdateChecks(env) {
|
|
|
5549
6238
|
function statusDeps(env) {
|
|
5550
6239
|
return {
|
|
5551
6240
|
platform: osPlatform4(),
|
|
5552
|
-
homeDir:
|
|
6241
|
+
homeDir: homedir7(),
|
|
5553
6242
|
env,
|
|
5554
6243
|
binPath: process.argv[1],
|
|
5555
6244
|
detectRuntimes: () => resolveRuntimes(env),
|
|
5556
|
-
fileExists: (path) =>
|
|
6245
|
+
fileExists: (path) => existsSync13(path),
|
|
5557
6246
|
capture: (argv) => {
|
|
5558
6247
|
const [cmd, ...args] = argv;
|
|
5559
6248
|
try {
|
|
5560
|
-
const stdout =
|
|
6249
|
+
const stdout = execFileSync9(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
5561
6250
|
return { code: 0, stdout };
|
|
5562
6251
|
} catch (e) {
|
|
5563
6252
|
const status = e.status;
|
|
@@ -5698,7 +6387,7 @@ var invokedAsEntrypoint = (() => {
|
|
|
5698
6387
|
const argv1 = process.argv[1];
|
|
5699
6388
|
if (!argv1) return false;
|
|
5700
6389
|
try {
|
|
5701
|
-
return pathToFileURL(
|
|
6390
|
+
return pathToFileURL(realpathSync5(argv1)).href === import.meta.url;
|
|
5702
6391
|
} catch {
|
|
5703
6392
|
return false;
|
|
5704
6393
|
}
|