dahrk-node 0.1.24 → 0.1.26
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 +483 -195
- package/package.json +4 -4
package/dist/main.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import "./chunk-FYS2JH42.js";
|
|
3
3
|
|
|
4
4
|
// src/main.ts
|
|
5
|
-
import { execFileSync as
|
|
6
|
-
import { existsSync as
|
|
5
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
6
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12, realpathSync as realpathSync5 } from "fs";
|
|
7
7
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
8
8
|
import { homedir as homedir7, platform as osPlatform4 } from "os";
|
|
9
|
-
import { basename as basename2, join as
|
|
9
|
+
import { basename as basename2, join as join20 } from "path";
|
|
10
10
|
import { pathToFileURL } from "url";
|
|
11
11
|
|
|
12
12
|
// ../../packages/edge/src/ws-client.ts
|
|
@@ -245,12 +245,12 @@ function documentsBlock(ctx) {
|
|
|
245
245
|
const truncated = body.length > cap;
|
|
246
246
|
const excerpt = truncated ? body.slice(0, cap) : body;
|
|
247
247
|
budget -= excerpt.length;
|
|
248
|
-
const
|
|
248
|
+
const tail3 = truncated ? `
|
|
249
249
|
...(truncated; full text at ${path})` : "";
|
|
250
250
|
const title = doc.title.replace(/[<>"]/g, "'");
|
|
251
251
|
parts.push(
|
|
252
252
|
`<document title="${title}" file="${path}">
|
|
253
|
-
${neutraliseDelimiters(excerpt)}${
|
|
253
|
+
${neutraliseDelimiters(excerpt)}${tail3}
|
|
254
254
|
</document>`
|
|
255
255
|
);
|
|
256
256
|
if (budget <= 0) break;
|
|
@@ -303,6 +303,18 @@ ${neutraliseGateFeedbackDelimiters(content)}
|
|
|
303
303
|
${parts.join("\n")}
|
|
304
304
|
</gate-feedback>`;
|
|
305
305
|
}
|
|
306
|
+
function checkFailuresBlock(ctx) {
|
|
307
|
+
const failures = ctx.checkFailures;
|
|
308
|
+
if (!failures) return "";
|
|
309
|
+
const { stageId, attempt, verifications } = failures;
|
|
310
|
+
if (verifications.length === 0) return "";
|
|
311
|
+
const safeStage = stageId.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
312
|
+
const rows = verifications.map((v) => ` ${v.name}: ${v.status === "failed" ? "FAILED" : v.status}`).join("\n");
|
|
313
|
+
return `<check-failures stage="${stageId.replace(/[<>"]/g, "'")}" attempt="${attempt}">
|
|
314
|
+
${rows}
|
|
315
|
+
Full output: .dahrk/scratch/checks/${safeStage}-${attempt}.md
|
|
316
|
+
</check-failures>`;
|
|
317
|
+
}
|
|
306
318
|
function resolveStagePrompt(ctx) {
|
|
307
319
|
const instruction = stageInstruction(ctx);
|
|
308
320
|
const ticket = ctx.issueContext?.trim() ? `<ticket>
|
|
@@ -311,14 +323,15 @@ ${ctx.issueContext.trim()}
|
|
|
311
323
|
const guidance = guidanceBlock(ctx);
|
|
312
324
|
const gateFeedback = gateFeedbackBlock(ctx);
|
|
313
325
|
const docs = documentsBlock(ctx);
|
|
314
|
-
const
|
|
326
|
+
const checkFailures = checkFailuresBlock(ctx);
|
|
327
|
+
const preamble = [ticket, guidance, gateFeedback, docs, checkFailures].filter(Boolean).join("\n\n");
|
|
315
328
|
return preamble ? `${preamble}
|
|
316
329
|
|
|
317
330
|
${instruction}` : instruction;
|
|
318
331
|
}
|
|
319
332
|
function hasSystemPrompt(ctx) {
|
|
320
333
|
return Boolean(
|
|
321
|
-
ctx.config.prompt || ctx.config.promptFile || ctx.issueContext?.trim() || ctx.guidance && ctx.guidance.length > 0 || ctx.gateFeedback && ctx.gateFeedback.length > 0 || ctx.attachedDocuments && ctx.attachedDocuments.length > 0
|
|
334
|
+
ctx.config.prompt || ctx.config.promptFile || ctx.issueContext?.trim() || ctx.guidance && ctx.guidance.length > 0 || ctx.gateFeedback && ctx.gateFeedback.length > 0 || Boolean(ctx.checkFailures) || ctx.attachedDocuments && ctx.attachedDocuments.length > 0
|
|
322
335
|
);
|
|
323
336
|
}
|
|
324
337
|
var OPENING_KICKOFF = "Begin now. Using the ticket context and your instructions already provided, ask the human your first question. Do not wait for further input before sending your first message.";
|
|
@@ -2564,6 +2577,222 @@ async function overlayComponents(opts) {
|
|
|
2564
2577
|
return result;
|
|
2565
2578
|
}
|
|
2566
2579
|
|
|
2580
|
+
// ../../packages/executor-worktree/src/repo-setup.ts
|
|
2581
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
2582
|
+
import { createHash as createHash3 } from "crypto";
|
|
2583
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
2584
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
2585
|
+
var noopLogger2 = { info: () => {
|
|
2586
|
+
}, warn: () => {
|
|
2587
|
+
} };
|
|
2588
|
+
var SCRATCH_DIR2 = join10(".dahrk", "scratch");
|
|
2589
|
+
var MARKER_NAME = ".setup-done";
|
|
2590
|
+
var DEFAULT_TIMEOUT_MS = 6e5;
|
|
2591
|
+
var OUTPUT_CAP = 16384;
|
|
2592
|
+
function digest(command) {
|
|
2593
|
+
return createHash3("sha256").update(command).digest("hex").slice(0, 16);
|
|
2594
|
+
}
|
|
2595
|
+
function tail(output) {
|
|
2596
|
+
return output.length > OUTPUT_CAP ? output.slice(output.length - OUTPUT_CAP) : output;
|
|
2597
|
+
}
|
|
2598
|
+
function runRepoSetup(opts) {
|
|
2599
|
+
const { worktreePath, command } = opts;
|
|
2600
|
+
const log = opts.log ?? noopLogger2;
|
|
2601
|
+
const markerPath = join10(worktreePath, SCRATCH_DIR2, MARKER_NAME);
|
|
2602
|
+
const want = digest(command);
|
|
2603
|
+
if (existsSync5(markerPath)) {
|
|
2604
|
+
try {
|
|
2605
|
+
if (readFileSync4(markerPath, "utf8").trim() === want) {
|
|
2606
|
+
log.info(`repo setup: cached (marker matches), skipping`);
|
|
2607
|
+
return { status: "cached" };
|
|
2608
|
+
}
|
|
2609
|
+
} catch {
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
log.info(`repo setup: running \`${command}\``);
|
|
2613
|
+
try {
|
|
2614
|
+
const stdout = execFileSync3("sh", ["-c", command], {
|
|
2615
|
+
cwd: worktreePath,
|
|
2616
|
+
env: opts.env ?? process.env,
|
|
2617
|
+
timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
2618
|
+
encoding: "utf8",
|
|
2619
|
+
// Fold stderr into stdout so the captured trace shows what the installer said, in order.
|
|
2620
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2621
|
+
});
|
|
2622
|
+
mkdirSync5(dirname4(markerPath), { recursive: true });
|
|
2623
|
+
writeFileSync6(markerPath, want);
|
|
2624
|
+
return { status: "ran", output: tail(stdout ?? "") };
|
|
2625
|
+
} catch (e) {
|
|
2626
|
+
const err = e;
|
|
2627
|
+
const combined = `${err.stdout?.toString() ?? ""}${err.stderr?.toString() ?? ""}` || e.message;
|
|
2628
|
+
log.warn(`repo setup failed (exit ${err.status ?? "null"})`);
|
|
2629
|
+
return { status: "failed", exitCode: err.status ?? null, output: tail(combined) };
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
// ../../packages/executor-worktree/src/check-runner.ts
|
|
2634
|
+
import { spawn } from "child_process";
|
|
2635
|
+
var DEFAULT_TIMEOUT_MS2 = 6e5;
|
|
2636
|
+
var OUTPUT_CAP2 = 16384;
|
|
2637
|
+
function tail2(output) {
|
|
2638
|
+
return output.length > OUTPUT_CAP2 ? output.slice(output.length - OUTPUT_CAP2) : output;
|
|
2639
|
+
}
|
|
2640
|
+
async function runOne(check, cwd, startedAt) {
|
|
2641
|
+
const timeoutMs = (check.timeoutSeconds ?? DEFAULT_TIMEOUT_MS2 / 1e3) * 1e3;
|
|
2642
|
+
return await new Promise((resolve3) => {
|
|
2643
|
+
let chunks = "";
|
|
2644
|
+
let timedOut = false;
|
|
2645
|
+
let settled = false;
|
|
2646
|
+
const child = spawn("sh", ["-c", check.command], { cwd, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
2647
|
+
const append = (buf) => {
|
|
2648
|
+
chunks = tail2(chunks + buf.toString("utf8"));
|
|
2649
|
+
};
|
|
2650
|
+
child.stdout?.on("data", append);
|
|
2651
|
+
child.stderr?.on("data", append);
|
|
2652
|
+
const timer = setTimeout(() => {
|
|
2653
|
+
timedOut = true;
|
|
2654
|
+
child.kill("SIGKILL");
|
|
2655
|
+
}, timeoutMs);
|
|
2656
|
+
const settle = (exitCode) => {
|
|
2657
|
+
if (settled) return;
|
|
2658
|
+
settled = true;
|
|
2659
|
+
clearTimeout(timer);
|
|
2660
|
+
resolve3({
|
|
2661
|
+
name: check.name,
|
|
2662
|
+
command: check.command,
|
|
2663
|
+
exitCode,
|
|
2664
|
+
output: chunks,
|
|
2665
|
+
status: exitCode === 0 ? "passed" : "failed",
|
|
2666
|
+
durationMs: Date.now() - startedAt,
|
|
2667
|
+
timedOut
|
|
2668
|
+
});
|
|
2669
|
+
};
|
|
2670
|
+
child.on("close", (code) => settle(code));
|
|
2671
|
+
child.on("error", (err) => {
|
|
2672
|
+
chunks = tail2(`${chunks}
|
|
2673
|
+
check could not start: ${err.message}`);
|
|
2674
|
+
settle(null);
|
|
2675
|
+
});
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
function summariseChecks(outcomes) {
|
|
2679
|
+
const failed = outcomes.filter((o) => o.status === "failed");
|
|
2680
|
+
const skipped = outcomes.filter((o) => o.status === "skipped");
|
|
2681
|
+
if (outcomes.length === 0) return "no checks declared";
|
|
2682
|
+
const suffix = skipped.length > 0 ? `; ${skipped.length} not run` : "";
|
|
2683
|
+
if (failed.length === 0) return `all ${outcomes.length} checks passed${suffix}`;
|
|
2684
|
+
return `${failed.length} of ${outcomes.length} checks failed: ${failed.map((o) => o.name).join(", ")}${suffix}`;
|
|
2685
|
+
}
|
|
2686
|
+
function createCheckRunner(checks, onOutcomes) {
|
|
2687
|
+
let cancelled = false;
|
|
2688
|
+
let lastSummary = "";
|
|
2689
|
+
return {
|
|
2690
|
+
// A check stage runs no agent, so claiming an agent runtime here would corrupt the trace record
|
|
2691
|
+
// the optimiser and the observability CLI both read.
|
|
2692
|
+
runtime: "check",
|
|
2693
|
+
async runBatch(ctx, onTrace) {
|
|
2694
|
+
const cwd = ctx.workspace.worktreePath;
|
|
2695
|
+
const outcomes = [];
|
|
2696
|
+
let seq = 0;
|
|
2697
|
+
for (const check of checks) {
|
|
2698
|
+
if (cancelled) {
|
|
2699
|
+
outcomes.push({
|
|
2700
|
+
name: check.name,
|
|
2701
|
+
command: check.command,
|
|
2702
|
+
exitCode: null,
|
|
2703
|
+
output: "",
|
|
2704
|
+
status: "skipped",
|
|
2705
|
+
durationMs: 0,
|
|
2706
|
+
timedOut: false
|
|
2707
|
+
});
|
|
2708
|
+
continue;
|
|
2709
|
+
}
|
|
2710
|
+
onTrace({
|
|
2711
|
+
seq: seq++,
|
|
2712
|
+
runtime: "check",
|
|
2713
|
+
type: "action",
|
|
2714
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2715
|
+
tool: "check",
|
|
2716
|
+
toolUseId: check.name,
|
|
2717
|
+
input: { name: check.name, command: check.command }
|
|
2718
|
+
});
|
|
2719
|
+
const outcome = await runOne(check, cwd, Date.now());
|
|
2720
|
+
outcomes.push(outcome);
|
|
2721
|
+
onTrace({
|
|
2722
|
+
seq: seq++,
|
|
2723
|
+
runtime: "check",
|
|
2724
|
+
type: "observation",
|
|
2725
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2726
|
+
toolUseId: check.name,
|
|
2727
|
+
isError: outcome.status === "failed",
|
|
2728
|
+
output: {
|
|
2729
|
+
name: outcome.name,
|
|
2730
|
+
exitCode: outcome.exitCode,
|
|
2731
|
+
timedOut: outcome.timedOut,
|
|
2732
|
+
durationMs: outcome.durationMs,
|
|
2733
|
+
output: outcome.output
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
lastSummary = summariseChecks(outcomes);
|
|
2738
|
+
onTrace({
|
|
2739
|
+
seq: seq++,
|
|
2740
|
+
runtime: "check",
|
|
2741
|
+
type: "response",
|
|
2742
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2743
|
+
text: lastSummary
|
|
2744
|
+
});
|
|
2745
|
+
onOutcomes?.(outcomes);
|
|
2746
|
+
const verifications = outcomes.map((o) => ({ name: o.name, status: o.status }));
|
|
2747
|
+
const anyFailed = outcomes.some((o) => o.status === "failed");
|
|
2748
|
+
return {
|
|
2749
|
+
status: cancelled ? "timeout" : anyFailed ? "fail" : "ok",
|
|
2750
|
+
verifications
|
|
2751
|
+
};
|
|
2752
|
+
},
|
|
2753
|
+
async runInteractive() {
|
|
2754
|
+
throw new Error("a check stage cannot be interactive");
|
|
2755
|
+
},
|
|
2756
|
+
/** Deterministic, and free: no model is asked to describe a lint run. */
|
|
2757
|
+
async summarise() {
|
|
2758
|
+
return lastSummary || summariseChecks([]);
|
|
2759
|
+
},
|
|
2760
|
+
async cancel() {
|
|
2761
|
+
cancelled = true;
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
var CHECK_NOTE_CAP_BYTES = 64 * 1024;
|
|
2766
|
+
var safeStageSegment = (stageId) => stageId.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
2767
|
+
function renderCheckNote(stageId, attempt, outcomes) {
|
|
2768
|
+
const failed = outcomes.filter((o) => o.status === "failed");
|
|
2769
|
+
const head = `# checks/${stageId} (attempt ${attempt}) - ${failed.length === 0 ? "PASSED" : "FAILED"}
|
|
2770
|
+
|
|
2771
|
+
${summariseChecks(outcomes)}
|
|
2772
|
+
`;
|
|
2773
|
+
const ordered = [...outcomes].sort((a, b) => Number(b.status === "failed") - Number(a.status === "failed"));
|
|
2774
|
+
let body = "";
|
|
2775
|
+
let budget = CHECK_NOTE_CAP_BYTES - head.length;
|
|
2776
|
+
for (const o of ordered) {
|
|
2777
|
+
const verdict2 = o.status === "skipped" ? "not run (the stage was cancelled before it started)" : o.timedOut ? `TIMED OUT after ${Math.round(o.durationMs / 1e3)}s` : `exit ${o.exitCode}`;
|
|
2778
|
+
const header = `
|
|
2779
|
+
## ${o.name} - ${o.status} (${verdict2})
|
|
2780
|
+
|
|
2781
|
+
$ ${o.command}
|
|
2782
|
+
`;
|
|
2783
|
+
const output = o.output.trim();
|
|
2784
|
+
const includeOutput = output.length > 0 && (o.status === "failed" || header.length + output.length < budget);
|
|
2785
|
+
const chunk = includeOutput ? `${header}
|
|
2786
|
+
\`\`\`
|
|
2787
|
+
${output}
|
|
2788
|
+
\`\`\`
|
|
2789
|
+
` : header;
|
|
2790
|
+
body += chunk;
|
|
2791
|
+
budget -= chunk.length;
|
|
2792
|
+
}
|
|
2793
|
+
return head + body;
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2567
2796
|
// ../../packages/executor-worktree/src/index.ts
|
|
2568
2797
|
function makeRunner(runtime) {
|
|
2569
2798
|
if ((process.env.DAHRK_RUNNER ?? process.env.SKAKEL_RUNNER ?? "real") === "mock") return createMockRunner(runtime);
|
|
@@ -2630,8 +2859,8 @@ function collectHealth(inputs) {
|
|
|
2630
2859
|
}
|
|
2631
2860
|
|
|
2632
2861
|
// ../../packages/edge/src/job-ledger.ts
|
|
2633
|
-
import { chmodSync, existsSync as
|
|
2634
|
-
import { dirname as
|
|
2862
|
+
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync, unlinkSync, writeFileSync as writeFileSync7 } from "fs";
|
|
2863
|
+
import { dirname as dirname5, join as join11 } from "path";
|
|
2635
2864
|
var FILE_MODE = 384;
|
|
2636
2865
|
var DIR_MODE = 448;
|
|
2637
2866
|
function nullJobLedger() {
|
|
@@ -2647,9 +2876,9 @@ var isEntry = (v) => {
|
|
|
2647
2876
|
};
|
|
2648
2877
|
function fileJobLedger(file, warn = console.warn) {
|
|
2649
2878
|
const read = () => {
|
|
2650
|
-
if (!
|
|
2879
|
+
if (!existsSync6(file)) return [];
|
|
2651
2880
|
try {
|
|
2652
|
-
const parsed = JSON.parse(
|
|
2881
|
+
const parsed = JSON.parse(readFileSync5(file, "utf8"));
|
|
2653
2882
|
if (!Array.isArray(parsed)) return [];
|
|
2654
2883
|
return parsed.filter(isEntry);
|
|
2655
2884
|
} catch {
|
|
@@ -2659,15 +2888,15 @@ function fileJobLedger(file, warn = console.warn) {
|
|
|
2659
2888
|
const write = (entries) => {
|
|
2660
2889
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
2661
2890
|
try {
|
|
2662
|
-
|
|
2663
|
-
|
|
2891
|
+
mkdirSync6(dirname5(file), { recursive: true, mode: DIR_MODE });
|
|
2892
|
+
writeFileSync7(tmp, `${JSON.stringify(entries, null, 2)}
|
|
2664
2893
|
`, { mode: FILE_MODE });
|
|
2665
2894
|
chmodSync(tmp, FILE_MODE);
|
|
2666
2895
|
renameSync(tmp, file);
|
|
2667
2896
|
} catch (e) {
|
|
2668
2897
|
warn(`could not persist the job ledger to ${file}: ${e.message}`);
|
|
2669
2898
|
try {
|
|
2670
|
-
if (
|
|
2899
|
+
if (existsSync6(tmp)) unlinkSync(tmp);
|
|
2671
2900
|
} catch {
|
|
2672
2901
|
}
|
|
2673
2902
|
}
|
|
@@ -2695,7 +2924,7 @@ function announceableJobs(entries) {
|
|
|
2695
2924
|
return out2;
|
|
2696
2925
|
}
|
|
2697
2926
|
function jobLedgerFile(stateDir2) {
|
|
2698
|
-
return
|
|
2927
|
+
return join11(stateDir2, "jobs.json");
|
|
2699
2928
|
}
|
|
2700
2929
|
|
|
2701
2930
|
// ../../packages/edge/src/log-shipper.ts
|
|
@@ -2798,8 +3027,8 @@ function ceilingFromEnv(env) {
|
|
|
2798
3027
|
}
|
|
2799
3028
|
|
|
2800
3029
|
// ../../packages/edge/src/logger.ts
|
|
2801
|
-
import { closeSync, existsSync as
|
|
2802
|
-
import { join as
|
|
3030
|
+
import { closeSync, existsSync as existsSync7, mkdirSync as mkdirSync7, openSync, renameSync as renameSync2, rmSync as rmSync5, statSync as statSync2, writeSync } from "fs";
|
|
3031
|
+
import { join as join12 } from "path";
|
|
2803
3032
|
import pino from "pino";
|
|
2804
3033
|
|
|
2805
3034
|
// ../../packages/edge/src/redact.ts
|
|
@@ -2895,7 +3124,7 @@ var RotatingFile = class {
|
|
|
2895
3124
|
open() {
|
|
2896
3125
|
try {
|
|
2897
3126
|
this.fd = openSync(this.path, "a");
|
|
2898
|
-
this.size =
|
|
3127
|
+
this.size = existsSync7(this.path) ? statSync2(this.path).size : 0;
|
|
2899
3128
|
} catch (e) {
|
|
2900
3129
|
this.disable(e);
|
|
2901
3130
|
}
|
|
@@ -2923,12 +3152,12 @@ var RotatingFile = class {
|
|
|
2923
3152
|
closeSync(this.fd);
|
|
2924
3153
|
this.fd = void 0;
|
|
2925
3154
|
const oldest = `${this.path}.${this.maxFiles}`;
|
|
2926
|
-
if (
|
|
3155
|
+
if (existsSync7(oldest)) rmSync5(oldest, { force: true });
|
|
2927
3156
|
for (let i = this.maxFiles - 1; i >= 1; i--) {
|
|
2928
3157
|
const from = `${this.path}.${i}`;
|
|
2929
|
-
if (
|
|
3158
|
+
if (existsSync7(from)) renameSync2(from, `${this.path}.${i + 1}`);
|
|
2930
3159
|
}
|
|
2931
|
-
if (
|
|
3160
|
+
if (existsSync7(this.path)) renameSync2(this.path, `${this.path}.1`);
|
|
2932
3161
|
this.open();
|
|
2933
3162
|
} catch (e) {
|
|
2934
3163
|
this.disable(e);
|
|
@@ -2988,8 +3217,8 @@ function createNodeLogger(opts = {}) {
|
|
|
2988
3217
|
if (level !== "silent") streams.push({ level, stream: humanSink });
|
|
2989
3218
|
if (opts.dir && fileLevel !== "silent") {
|
|
2990
3219
|
try {
|
|
2991
|
-
|
|
2992
|
-
const file = new RotatingFile(
|
|
3220
|
+
mkdirSync7(opts.dir, { recursive: true, mode: 448 });
|
|
3221
|
+
const file = new RotatingFile(join12(opts.dir, "node.jsonl"), opts.maxBytes ?? 10 * 1024 * 1024, opts.maxFiles ?? 5);
|
|
2993
3222
|
streams.push({ level: fileLevel, stream: file });
|
|
2994
3223
|
} catch (e) {
|
|
2995
3224
|
process.stderr.write(`dahrk: file logging disabled (${e.message})
|
|
@@ -3045,21 +3274,21 @@ function denyToolRule(tool3) {
|
|
|
3045
3274
|
}
|
|
3046
3275
|
|
|
3047
3276
|
// ../../packages/edge/src/stage-runner.ts
|
|
3048
|
-
import { execFileSync as
|
|
3049
|
-
import { createHash as
|
|
3050
|
-
import { mkdirSync as
|
|
3277
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
3278
|
+
import { createHash as createHash4 } from "crypto";
|
|
3279
|
+
import { mkdirSync as mkdirSync8, readdirSync as readdirSync3, readFileSync as readFileSync6, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
3051
3280
|
import { tmpdir as tmpdir5 } from "os";
|
|
3052
|
-
import { isAbsolute as isAbsolute3, join as
|
|
3053
|
-
import { attachedDocBasename as attachedDocBasename2 } from "@dahrk/contracts";
|
|
3281
|
+
import { isAbsolute as isAbsolute3, join as join14, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
|
|
3282
|
+
import { attachedDocBasename as attachedDocBasename2, isCheckJob } from "@dahrk/contracts";
|
|
3054
3283
|
|
|
3055
3284
|
// ../../packages/edge/src/builtins.ts
|
|
3056
|
-
import { execFileSync as
|
|
3285
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
3057
3286
|
|
|
3058
3287
|
// ../../packages/edge/src/fs-roots.ts
|
|
3059
|
-
import { execFileSync as
|
|
3060
|
-
import { existsSync as
|
|
3288
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
3289
|
+
import { existsSync as existsSync8, realpathSync as realpathSync2 } from "fs";
|
|
3061
3290
|
import { homedir as homedir3, tmpdir as tmpdir4 } from "os";
|
|
3062
|
-
import { dirname as
|
|
3291
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join13, relative as relative2, resolve, sep as sep2 } from "path";
|
|
3063
3292
|
function isUnder(root, target) {
|
|
3064
3293
|
const rel = relative2(resolve(root), resolve(target));
|
|
3065
3294
|
return rel === "" || !rel.startsWith(`..${sep2}`) && rel !== ".." && !isAbsolute2(rel);
|
|
@@ -3071,17 +3300,17 @@ function expandPath(raw, cwd) {
|
|
|
3071
3300
|
}
|
|
3072
3301
|
function realish(p) {
|
|
3073
3302
|
let head = p;
|
|
3074
|
-
const
|
|
3075
|
-
while (head !==
|
|
3076
|
-
if (
|
|
3303
|
+
const tail3 = [];
|
|
3304
|
+
while (head !== dirname6(head)) {
|
|
3305
|
+
if (existsSync8(head)) {
|
|
3077
3306
|
try {
|
|
3078
|
-
return
|
|
3307
|
+
return join13(realpathSync2.native(head), ...tail3.reverse());
|
|
3079
3308
|
} catch {
|
|
3080
3309
|
return p;
|
|
3081
3310
|
}
|
|
3082
3311
|
}
|
|
3083
|
-
|
|
3084
|
-
head =
|
|
3312
|
+
tail3.push(head.slice(dirname6(head).length + 1));
|
|
3313
|
+
head = dirname6(head);
|
|
3085
3314
|
}
|
|
3086
3315
|
return p;
|
|
3087
3316
|
}
|
|
@@ -3095,7 +3324,7 @@ function withinRoots(raw, roots, need) {
|
|
|
3095
3324
|
}
|
|
3096
3325
|
function gitCommonDir(worktreePath) {
|
|
3097
3326
|
try {
|
|
3098
|
-
const out2 =
|
|
3327
|
+
const out2 = execFileSync4("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
3099
3328
|
cwd: worktreePath,
|
|
3100
3329
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3101
3330
|
}).toString().trim();
|
|
@@ -3108,7 +3337,7 @@ var pnpmStoreCache;
|
|
|
3108
3337
|
function pnpmStore() {
|
|
3109
3338
|
if (pnpmStoreCache) return pnpmStoreCache.path;
|
|
3110
3339
|
try {
|
|
3111
|
-
const out2 =
|
|
3340
|
+
const out2 = execFileSync4("pnpm", ["store", "path"], { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }).toString().trim();
|
|
3112
3341
|
pnpmStoreCache = { path: out2 || void 0 };
|
|
3113
3342
|
} catch {
|
|
3114
3343
|
pnpmStoreCache = { path: void 0 };
|
|
@@ -3144,26 +3373,26 @@ function computeFsRoots(opts) {
|
|
|
3144
3373
|
"/System",
|
|
3145
3374
|
"/proc",
|
|
3146
3375
|
"/sys",
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3376
|
+
join13(home, ".gitconfig"),
|
|
3377
|
+
join13(home, ".config"),
|
|
3378
|
+
join13(home, ".npmrc"),
|
|
3379
|
+
join13(home, ".cache"),
|
|
3380
|
+
join13(home, "Library", "Caches"),
|
|
3381
|
+
join13(home, "Library", "pnpm"),
|
|
3382
|
+
join13(home, ".local", "share"),
|
|
3383
|
+
join13(home, ".nvm"),
|
|
3384
|
+
join13(home, ".volta"),
|
|
3385
|
+
join13(home, ".asdf"),
|
|
3386
|
+
join13(home, ".cargo"),
|
|
3387
|
+
join13(home, ".rustup"),
|
|
3159
3388
|
...splitRoots(process.env.DAHRK_FS_EXTRA_READ_ROOTS).map((p) => realish(resolve(p)))
|
|
3160
3389
|
].map(realish);
|
|
3161
3390
|
const deny = [
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3391
|
+
join13(home, ".ssh"),
|
|
3392
|
+
join13(home, ".aws"),
|
|
3393
|
+
join13(home, ".gnupg"),
|
|
3394
|
+
join13(home, ".config", "gcloud"),
|
|
3395
|
+
join13(home, "Library", "Keychains"),
|
|
3167
3396
|
"/Volumes",
|
|
3168
3397
|
"/etc/shadow",
|
|
3169
3398
|
"/etc/sudoers"
|
|
@@ -3515,7 +3744,7 @@ function isDangerousRm(cmd) {
|
|
|
3515
3744
|
}
|
|
3516
3745
|
function currentBranch(worktreePath) {
|
|
3517
3746
|
try {
|
|
3518
|
-
return
|
|
3747
|
+
return execFileSync5("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreePath }).toString().trim();
|
|
3519
3748
|
} catch {
|
|
3520
3749
|
return "";
|
|
3521
3750
|
}
|
|
@@ -3817,34 +4046,34 @@ var attemptOf = (jobId) => {
|
|
|
3817
4046
|
const m = /-(\d+)$/.exec(jobId);
|
|
3818
4047
|
return m ? Number(m[1]) : 1;
|
|
3819
4048
|
};
|
|
3820
|
-
var
|
|
4049
|
+
var digest2 = (value) => `sha256:${createHash4("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16)}`;
|
|
3821
4050
|
function writeScratchState(ref, job, attempt, status) {
|
|
3822
|
-
const statePath =
|
|
4051
|
+
const statePath = join14(ref.scratchPath, "state.json");
|
|
3823
4052
|
let state;
|
|
3824
4053
|
try {
|
|
3825
|
-
state = JSON.parse(
|
|
4054
|
+
state = JSON.parse(readFileSync6(statePath, "utf8"));
|
|
3826
4055
|
} catch {
|
|
3827
4056
|
state = { runId: job.runId, tenantId: job.tenantId, stages: {} };
|
|
3828
4057
|
}
|
|
3829
4058
|
state.stages[job.stageId] = { currentAttempt: attempt, status };
|
|
3830
|
-
|
|
3831
|
-
|
|
4059
|
+
mkdirSync8(ref.scratchPath, { recursive: true });
|
|
4060
|
+
writeFileSync8(statePath, JSON.stringify(state, null, 2));
|
|
3832
4061
|
}
|
|
3833
4062
|
function writeIssueContext(ref, issueContext) {
|
|
3834
4063
|
if (issueContext === void 0) return;
|
|
3835
4064
|
try {
|
|
3836
|
-
|
|
3837
|
-
|
|
4065
|
+
mkdirSync8(ref.scratchPath, { recursive: true });
|
|
4066
|
+
writeFileSync8(join14(ref.scratchPath, "issue.md"), issueContext);
|
|
3838
4067
|
} catch {
|
|
3839
4068
|
}
|
|
3840
4069
|
}
|
|
3841
4070
|
function writeAttachedDocuments(ref, docs) {
|
|
3842
4071
|
if (!docs || docs.length === 0) return;
|
|
3843
4072
|
try {
|
|
3844
|
-
const dir =
|
|
3845
|
-
|
|
4073
|
+
const dir = join14(ref.scratchPath, "docs");
|
|
4074
|
+
mkdirSync8(dir, { recursive: true });
|
|
3846
4075
|
for (const doc of docs) {
|
|
3847
|
-
|
|
4076
|
+
writeFileSync8(join14(dir, `${attachedDocBasename2(doc)}.md`), doc.content);
|
|
3848
4077
|
}
|
|
3849
4078
|
} catch {
|
|
3850
4079
|
}
|
|
@@ -3862,8 +4091,17 @@ ${lines.join("\n")}
|
|
|
3862
4091
|
function writeGuidance(ref, guidance) {
|
|
3863
4092
|
if (!guidance || guidance.length === 0) return;
|
|
3864
4093
|
try {
|
|
3865
|
-
|
|
3866
|
-
|
|
4094
|
+
mkdirSync8(ref.scratchPath, { recursive: true });
|
|
4095
|
+
writeFileSync8(join14(ref.scratchPath, "guidance.md"), renderGuidanceMarkdown(guidance));
|
|
4096
|
+
} catch {
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
function writeCheckNote(ref, stageId, attempt, outcomes) {
|
|
4100
|
+
if (outcomes.length === 0) return;
|
|
4101
|
+
try {
|
|
4102
|
+
const dir = join14(ref.scratchPath, "checks");
|
|
4103
|
+
mkdirSync8(dir, { recursive: true });
|
|
4104
|
+
writeFileSync8(join14(dir, `${safeStageSegment(stageId)}-${attempt}.md`), renderCheckNote(stageId, attempt, outcomes));
|
|
3867
4105
|
} catch {
|
|
3868
4106
|
}
|
|
3869
4107
|
}
|
|
@@ -3889,7 +4127,7 @@ function readEmittedArtifact(ref, relPath) {
|
|
|
3889
4127
|
const path = resolveWorktreeRelativePath(ref, relPath);
|
|
3890
4128
|
if (!path) return void 0;
|
|
3891
4129
|
try {
|
|
3892
|
-
const raw =
|
|
4130
|
+
const raw = readFileSync6(path, "utf8");
|
|
3893
4131
|
return { path: relPath, content: capContent(raw) };
|
|
3894
4132
|
} catch {
|
|
3895
4133
|
return void 0;
|
|
@@ -3898,12 +4136,12 @@ function readEmittedArtifact(ref, relPath) {
|
|
|
3898
4136
|
function scanScratchOutput(ref, preferRel) {
|
|
3899
4137
|
const preferBase = preferRel?.split("/").pop();
|
|
3900
4138
|
try {
|
|
3901
|
-
const names = readdirSync3(
|
|
4139
|
+
const names = readdirSync3(join14(ref.worktreePath, SCRATCH_OUTPUT_DIR)).filter(
|
|
3902
4140
|
(n) => n.toLowerCase().endsWith(".md")
|
|
3903
4141
|
);
|
|
3904
4142
|
if (names.length === 0) return void 0;
|
|
3905
4143
|
const pick = preferBase && names.includes(preferBase) ? preferBase : names[0];
|
|
3906
|
-
const raw =
|
|
4144
|
+
const raw = readFileSync6(join14(ref.worktreePath, SCRATCH_OUTPUT_DIR, pick), "utf8");
|
|
3907
4145
|
if (raw.trim().length === 0) return void 0;
|
|
3908
4146
|
return { path: `${SCRATCH_OUTPUT_DIR}/${pick}`, content: capContent(raw) };
|
|
3909
4147
|
} catch {
|
|
@@ -3913,7 +4151,7 @@ function scanScratchOutput(ref, preferRel) {
|
|
|
3913
4151
|
function scanChangedMarkdown(ref) {
|
|
3914
4152
|
const git = (args) => {
|
|
3915
4153
|
try {
|
|
3916
|
-
return
|
|
4154
|
+
return execFileSync6("git", ["-C", ref.worktreePath, ...args], { encoding: "utf8" }).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
3917
4155
|
} catch {
|
|
3918
4156
|
return [];
|
|
3919
4157
|
}
|
|
@@ -3923,7 +4161,7 @@ function scanChangedMarkdown(ref) {
|
|
|
3923
4161
|
);
|
|
3924
4162
|
for (const rel of rels) {
|
|
3925
4163
|
try {
|
|
3926
|
-
const raw =
|
|
4164
|
+
const raw = readFileSync6(join14(ref.worktreePath, rel), "utf8");
|
|
3927
4165
|
if (raw.trim().length > 0) return { path: rel, content: capContent(raw) };
|
|
3928
4166
|
} catch {
|
|
3929
4167
|
}
|
|
@@ -4015,7 +4253,10 @@ function createStageRunner(deps) {
|
|
|
4015
4253
|
return reaper.reap(reaperDryRun ? { dryRun: true } : {});
|
|
4016
4254
|
},
|
|
4017
4255
|
async runJob(job) {
|
|
4018
|
-
const { stageId, jobId, runId
|
|
4256
|
+
const { stageId, jobId, runId } = job;
|
|
4257
|
+
const isCheck = isCheckJob(job);
|
|
4258
|
+
const agentConfig = job.agentConfig;
|
|
4259
|
+
const traceRuntime = isCheck ? "check" : agentConfig.runtime;
|
|
4019
4260
|
const attempt = attemptOf(jobId);
|
|
4020
4261
|
if (deps.tenantId && job.tenantId !== deps.tenantId) {
|
|
4021
4262
|
return {
|
|
@@ -4049,10 +4290,10 @@ function createStageRunner(deps) {
|
|
|
4049
4290
|
...job.workspaceRef.credentialToken ? { credentialToken: job.workspaceRef.credentialToken } : {}
|
|
4050
4291
|
});
|
|
4051
4292
|
} else {
|
|
4052
|
-
const base = deps.scratchRoot ??
|
|
4053
|
-
const worktreePath =
|
|
4054
|
-
const scratchPath =
|
|
4055
|
-
|
|
4293
|
+
const base = deps.scratchRoot ?? join14(tmpdir5(), "dahrk", "scratch");
|
|
4294
|
+
const worktreePath = join14(base, runId);
|
|
4295
|
+
const scratchPath = join14(worktreePath, ".dahrk", "scratch");
|
|
4296
|
+
mkdirSync8(scratchPath, { recursive: true });
|
|
4056
4297
|
ref = { repoId: "", gitUrl: "", repo: "", baseBranch: "", worktreePath, scratchPath };
|
|
4057
4298
|
scratchOnly.add(runId);
|
|
4058
4299
|
}
|
|
@@ -4062,12 +4303,12 @@ function createStageRunner(deps) {
|
|
|
4062
4303
|
writeIssueContext(ref, job.issueContext);
|
|
4063
4304
|
writeGuidance(ref, job.guidance);
|
|
4064
4305
|
writeAttachedDocuments(ref, job.attachedDocuments);
|
|
4065
|
-
if (
|
|
4066
|
-
const artifactDir = resolveWorktreeRelativePath(ref,
|
|
4067
|
-
const slash =
|
|
4306
|
+
if (agentConfig?.emitArtifact) {
|
|
4307
|
+
const artifactDir = resolveWorktreeRelativePath(ref, agentConfig.emitArtifact);
|
|
4308
|
+
const slash = agentConfig.emitArtifact.lastIndexOf("/");
|
|
4068
4309
|
if (artifactDir && slash > 0) {
|
|
4069
4310
|
try {
|
|
4070
|
-
|
|
4311
|
+
mkdirSync8(join14(ref.worktreePath, agentConfig.emitArtifact.slice(0, slash)), { recursive: true });
|
|
4071
4312
|
} catch {
|
|
4072
4313
|
}
|
|
4073
4314
|
}
|
|
@@ -4079,24 +4320,24 @@ function createStageRunner(deps) {
|
|
|
4079
4320
|
stageId,
|
|
4080
4321
|
jobId,
|
|
4081
4322
|
attempt,
|
|
4082
|
-
runtime:
|
|
4083
|
-
model: agentConfig
|
|
4323
|
+
runtime: traceRuntime,
|
|
4324
|
+
model: agentConfig?.model,
|
|
4084
4325
|
sessionId: job.sessionId,
|
|
4085
|
-
configDigest:
|
|
4326
|
+
configDigest: digest2(isCheck ? job.checks : agentConfig),
|
|
4086
4327
|
startedAt: nowIso2()
|
|
4087
4328
|
};
|
|
4088
4329
|
const writer = createTraceWriter(ref.scratchPath, meta);
|
|
4089
4330
|
const streamEvent = (e) => deps.trace?.event({ runId, stageId, attempt, tenantId: job.tenantId, event: e });
|
|
4090
4331
|
streamEvent(
|
|
4091
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4332
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "attempt-start" })
|
|
4092
4333
|
);
|
|
4093
4334
|
const shipFinalTrace = async (finalMeta) => {
|
|
4094
4335
|
const sink = deps.trace;
|
|
4095
4336
|
if (!sink) return;
|
|
4096
4337
|
const base = { tenantId: job.tenantId, runId, stageId, attempt };
|
|
4097
4338
|
try {
|
|
4098
|
-
for (const name of readdirSync3(
|
|
4099
|
-
const bytes =
|
|
4339
|
+
for (const name of readdirSync3(join14(writer.dir, "blobs"))) {
|
|
4340
|
+
const bytes = readFileSync6(join14(writer.dir, "blobs", name));
|
|
4100
4341
|
const { url } = await sink.requestBlobUrl({
|
|
4101
4342
|
...base,
|
|
4102
4343
|
sha256: name,
|
|
@@ -4110,8 +4351,8 @@ function createStageRunner(deps) {
|
|
|
4110
4351
|
}
|
|
4111
4352
|
let archiveKey;
|
|
4112
4353
|
try {
|
|
4113
|
-
const bytes =
|
|
4114
|
-
const sha =
|
|
4354
|
+
const bytes = readFileSync6(join14(writer.dir, "trace.jsonl"));
|
|
4355
|
+
const sha = createHash4("sha256").update(bytes).digest("hex");
|
|
4115
4356
|
const { key, url } = await sink.requestBlobUrl({
|
|
4116
4357
|
...base,
|
|
4117
4358
|
sha256: sha,
|
|
@@ -4125,13 +4366,13 @@ function createStageRunner(deps) {
|
|
|
4125
4366
|
}
|
|
4126
4367
|
sink.finalised({ ...base, meta: finalMeta, eventCount: writer.count(), ...archiveKey ? { archiveKey } : {} });
|
|
4127
4368
|
};
|
|
4128
|
-
const finish = async (status2, summary2, sessionId, costUsd, handedBackDoc, failureClass2) => {
|
|
4369
|
+
const finish = async (status2, summary2, sessionId, costUsd, handedBackDoc, failureClass2, verifications) => {
|
|
4129
4370
|
active.delete(jobId);
|
|
4130
4371
|
turnQueues.delete(jobId);
|
|
4131
4372
|
await gateway?.stop().catch((e) => log.warn({ err: e, jobId }, "mcp gateway: stop failed"));
|
|
4132
4373
|
gateway = void 0;
|
|
4133
4374
|
streamEvent(
|
|
4134
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4375
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "stage-exit", status: status2 })
|
|
4135
4376
|
);
|
|
4136
4377
|
const endedAt = nowIso2();
|
|
4137
4378
|
writer.finalise({ status: status2, endedAt, ...sessionId ? { sessionId } : {} });
|
|
@@ -4148,12 +4389,12 @@ function createStageRunner(deps) {
|
|
|
4148
4389
|
);
|
|
4149
4390
|
lastUsed.set(runId, Date.now());
|
|
4150
4391
|
await applyRetention(runId).catch((e) => log.warn({ err: e, runId }, "retention: pass failed"));
|
|
4151
|
-
const wantsArtifact = agentConfig
|
|
4152
|
-
const resolved = status2 === "ok" && wantsArtifact ? resolveStageArtifact(ref, agentConfig
|
|
4392
|
+
const wantsArtifact = agentConfig?.emitArtifact !== void 0 || handedBackDoc !== void 0;
|
|
4393
|
+
const resolved = status2 === "ok" && wantsArtifact ? resolveStageArtifact(ref, agentConfig?.emitArtifact, handedBackDoc) : void 0;
|
|
4153
4394
|
if (status2 === "ok" && wantsArtifact) {
|
|
4154
4395
|
const detail = resolved ? `source=${resolved.source} path=${resolved.artifact.path} bytes=${resolved.artifact.content.length}` : "no document resolved (declared path, tool handoff, and scratch/changed-file scans all empty)";
|
|
4155
4396
|
streamEvent(
|
|
4156
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4397
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "artifact", detail })
|
|
4157
4398
|
);
|
|
4158
4399
|
}
|
|
4159
4400
|
return {
|
|
@@ -4163,7 +4404,12 @@ function createStageRunner(deps) {
|
|
|
4163
4404
|
...sessionId ? { sessionId } : {},
|
|
4164
4405
|
...costUsd !== void 0 ? { costUsd } : {},
|
|
4165
4406
|
...resolved ? { artifact: resolved.artifact } : {},
|
|
4166
|
-
...failureClass2 ? { failureClass: failureClass2 } : {}
|
|
4407
|
+
...failureClass2 ? { failureClass: failureClass2 } : {},
|
|
4408
|
+
// DHK-666: `JobResult.verifications` shipped with a contract, a projection fold, a Card
|
|
4409
|
+
// renderer and tests - but this object never forwarded it, which is the entire reason the
|
|
4410
|
+
// field has had no producer. Forwarded for EVERY stage, not just check jobs, so a hook or
|
|
4411
|
+
// an agent-run gate can populate it too.
|
|
4412
|
+
...verifications?.length ? { verifications } : {}
|
|
4167
4413
|
};
|
|
4168
4414
|
};
|
|
4169
4415
|
if (deps.packCache && job.provision && job.provision.length > 0) {
|
|
@@ -4176,17 +4422,35 @@ function createStageRunner(deps) {
|
|
|
4176
4422
|
});
|
|
4177
4423
|
const detail = `provision: ${overlay.written.length} written, ${overlay.skippedRepoLocal.length} repo-local, ${overlay.warnings.length} warning(s)`;
|
|
4178
4424
|
streamEvent(
|
|
4179
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4425
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "provision", detail })
|
|
4180
4426
|
);
|
|
4181
4427
|
const noteText = overlay.warnings.length > 0 ? `${detail}; ${overlay.warnings.join("; ")}` : detail;
|
|
4182
4428
|
deps.sendProgress({ jobId, kind: "observation", ts: nowIso2(), text: noteText });
|
|
4183
4429
|
} catch (e) {
|
|
4184
4430
|
const msg = `component provisioning failed: ${e.message}`;
|
|
4185
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime:
|
|
4431
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime: traceRuntime, kind: "provision-failed", message: msg });
|
|
4186
4432
|
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: msg });
|
|
4187
4433
|
return finish("fail", `${stageId}: ${msg}`, job.sessionId);
|
|
4188
4434
|
}
|
|
4189
4435
|
}
|
|
4436
|
+
const setup = job.workspaceRef?.setup;
|
|
4437
|
+
if (setup?.command && ref) {
|
|
4438
|
+
const outcome = runRepoSetup({ worktreePath: ref.worktreePath, command: setup.command });
|
|
4439
|
+
if (outcome.status === "failed") {
|
|
4440
|
+
const msg = `repo setup failed (exit ${outcome.exitCode ?? "null"})`;
|
|
4441
|
+
const detail2 = outcome.output ? `${msg}: ${outcome.output}` : msg;
|
|
4442
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime: traceRuntime, kind: "setup-failed", message: detail2 });
|
|
4443
|
+
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: detail2 });
|
|
4444
|
+
return finish("fail", `${stageId}: ${msg}`, job.sessionId, void 0, void 0, "harness");
|
|
4445
|
+
}
|
|
4446
|
+
const detail = outcome.status === "cached" ? "setup: cached (already installed)" : `setup: ran (exit 0, ${outcome.output.length} bytes)`;
|
|
4447
|
+
streamEvent(
|
|
4448
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "setup", detail })
|
|
4449
|
+
);
|
|
4450
|
+
if (outcome.status === "ran") {
|
|
4451
|
+
deps.sendProgress({ jobId, kind: "observation", ts: nowIso2(), text: outcome.output || detail });
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4190
4454
|
let counter = runToolCalls.get(runId);
|
|
4191
4455
|
if (!counter) {
|
|
4192
4456
|
counter = { count: 0 };
|
|
@@ -4204,14 +4468,14 @@ function createStageRunner(deps) {
|
|
|
4204
4468
|
const rules = [...jobRules, ...deps.rules];
|
|
4205
4469
|
const entry = evaluatePolicies({ kind: "stage-entry", stageId }, rules);
|
|
4206
4470
|
if (entry.verdict === "deny") {
|
|
4207
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4471
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "policy-deny", detail: entry.reason });
|
|
4208
4472
|
return finish("fail", `${stageId}: denied at stage entry (${entry.policy})`, job.sessionId);
|
|
4209
4473
|
}
|
|
4210
4474
|
let denied = false;
|
|
4211
4475
|
const surfacedDenyReasons = /* @__PURE__ */ new Set();
|
|
4212
4476
|
let escapedUnblocked = false;
|
|
4213
4477
|
const authorisedActions = [];
|
|
4214
|
-
const runtime = agentConfig
|
|
4478
|
+
const runtime = agentConfig?.runtime;
|
|
4215
4479
|
const actionKey = (tool3, input) => {
|
|
4216
4480
|
try {
|
|
4217
4481
|
return `${tool3}\0${JSON.stringify(input)}`;
|
|
@@ -4224,9 +4488,9 @@ function createStageRunner(deps) {
|
|
|
4224
4488
|
denied = true;
|
|
4225
4489
|
const reason = policyReason(verdict2);
|
|
4226
4490
|
if (toolUseId) {
|
|
4227
|
-
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "observation", runtime, toolUseId, isError: true, output: { error: reason } }));
|
|
4491
|
+
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "observation", runtime: traceRuntime, toolUseId, isError: true, output: { error: reason } }));
|
|
4228
4492
|
}
|
|
4229
|
-
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime, event: "policy-deny", detail: reason }));
|
|
4493
|
+
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "policy-deny", detail: reason }));
|
|
4230
4494
|
const surfaceKey = `${verdict2.policy}\0${reason}`;
|
|
4231
4495
|
if (!surfacedDenyReasons.has(surfaceKey)) {
|
|
4232
4496
|
surfacedDenyReasons.add(surfaceKey);
|
|
@@ -4267,19 +4531,28 @@ function createStageRunner(deps) {
|
|
|
4267
4531
|
streamEvent(writer.append(event));
|
|
4268
4532
|
if (event.type !== "state") deps.sendProgress({ jobId, kind: event.type, ts: event.ts, ...previewOf(event) });
|
|
4269
4533
|
};
|
|
4270
|
-
const mcpServers = agentConfig
|
|
4271
|
-
if (mcpServers && mcpServers.length > 0 && runtimeUsesMcpGateway(runtime)) {
|
|
4534
|
+
const mcpServers = agentConfig?.mcpServers;
|
|
4535
|
+
if (runtime && mcpServers && mcpServers.length > 0 && runtimeUsesMcpGateway(runtime)) {
|
|
4272
4536
|
gateway = await startMcpGateway({ servers: mcpServers, creds: job.brokeredCreds ?? {} });
|
|
4273
4537
|
}
|
|
4274
|
-
|
|
4538
|
+
let checkOutcomes = [];
|
|
4539
|
+
const runner = isCheck ? (deps.makeCheckRunner ?? createCheckRunner)(job.checks, (o) => {
|
|
4540
|
+
checkOutcomes = o;
|
|
4541
|
+
}) : deps.makeRunner(runtime);
|
|
4275
4542
|
active.set(jobId, runner);
|
|
4276
4543
|
const ctx = {
|
|
4277
|
-
config
|
|
4544
|
+
// A check runner reads only `workspace.worktreePath`; the empty config keeps the shared
|
|
4545
|
+
// RunnerContext shape without inventing a runtime the stage does not have.
|
|
4546
|
+
config: agentConfig ?? { runtime: "claude-code" },
|
|
4278
4547
|
workspace: ref,
|
|
4279
4548
|
sessionId: job.sessionId,
|
|
4280
4549
|
...job.issueContext !== void 0 ? { issueContext: job.issueContext } : {},
|
|
4281
4550
|
...job.guidance !== void 0 ? { guidance: job.guidance } : {},
|
|
4282
4551
|
...job.gateFeedback !== void 0 ? { gateFeedback: job.gateFeedback } : {},
|
|
4552
|
+
// Why this stage is running again, when it is. Without this passthrough the failing checks
|
|
4553
|
+
// reach the node and stop dead here, and the looped-back agent gets the stage's static prompt
|
|
4554
|
+
// with no hint of what it is meant to fix.
|
|
4555
|
+
...job.checkFailures !== void 0 ? { checkFailures: job.checkFailures } : {},
|
|
4283
4556
|
...job.attachedDocuments !== void 0 ? { attachedDocuments: job.attachedDocuments } : {},
|
|
4284
4557
|
...gateway ? { mcpProxyBaseUrl: gateway.baseUrl } : {},
|
|
4285
4558
|
// brokered inference env for a managed node (no operator login). The runtime adapter
|
|
@@ -4291,9 +4564,8 @@ function createStageRunner(deps) {
|
|
|
4291
4564
|
// auth above belongs to, plus the model fallback. The adapter applies nothing for a provider
|
|
4292
4565
|
// the hint does not name, so without this passthrough `runtimeEnv` arrives and is ignored -
|
|
4293
4566
|
// a managed node then has no inference auth at all and falls through to whatever provider the
|
|
4294
|
-
// runtime defaults to.
|
|
4295
|
-
//
|
|
4296
|
-
// both drop the cast when contracts ships.
|
|
4567
|
+
// runtime defaults to. A plain typed read since `@dahrk/contracts` declares `runtimeAuth` on
|
|
4568
|
+
// both `JobRequest` and `RunnerContext`.
|
|
4297
4569
|
...job.runtimeAuth ? { runtimeAuth: job.runtimeAuth } : {},
|
|
4298
4570
|
// The adapter persists each runtime-native record under the attempt's raw/ sidecar
|
|
4299
4571
|
// and stamps the rawRef onto the emitted event.
|
|
@@ -4310,7 +4582,7 @@ function createStageRunner(deps) {
|
|
|
4310
4582
|
})
|
|
4311
4583
|
} : {}
|
|
4312
4584
|
};
|
|
4313
|
-
const interactive = agentConfig
|
|
4585
|
+
const interactive = agentConfig?.interaction === "interactive";
|
|
4314
4586
|
let result;
|
|
4315
4587
|
let timedOut = false;
|
|
4316
4588
|
const killMs = Math.max(0, Math.floor((job.timeout ?? 0) * 1e3));
|
|
@@ -4321,7 +4593,7 @@ function createStageRunner(deps) {
|
|
|
4321
4593
|
let stalled = false;
|
|
4322
4594
|
let stallTimer;
|
|
4323
4595
|
const stallSource = agentConfig.stallMs ?? Number(process.env.DAHRK_BATCH_STALL_MS ?? process.env.SKAKEL_BATCH_STALL_MS ?? 3e5);
|
|
4324
|
-
const stallMs = interactive ? 0 : Math.max(0, Math.floor(stallSource));
|
|
4596
|
+
const stallMs = interactive || isCheck ? 0 : Math.max(0, Math.floor(stallSource));
|
|
4325
4597
|
if (stallMs > 0) {
|
|
4326
4598
|
bumpStall = () => {
|
|
4327
4599
|
if (stallTimer) clearTimeout(stallTimer);
|
|
@@ -4348,16 +4620,16 @@ function createStageRunner(deps) {
|
|
|
4348
4620
|
if (status === "ok" && escapedUnblocked) {
|
|
4349
4621
|
status = "fail";
|
|
4350
4622
|
const msg = `stage reached outside the run's worktree and the ${runtime} runtime could not block it before it ran`;
|
|
4351
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime, kind: "fs-confine-escape", message: msg });
|
|
4623
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime: traceRuntime, kind: "fs-confine-escape", message: msg });
|
|
4352
4624
|
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: msg });
|
|
4353
4625
|
}
|
|
4354
4626
|
if (status === "ok" && job.hooks && job.hooks.length > 0) {
|
|
4355
4627
|
for (const cmd of job.hooks) {
|
|
4356
4628
|
try {
|
|
4357
|
-
|
|
4629
|
+
execFileSync6("sh", ["-c", cmd], { cwd: ref.worktreePath, stdio: ["pipe", "pipe", "pipe"] });
|
|
4358
4630
|
} catch (e) {
|
|
4359
4631
|
status = "fail";
|
|
4360
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime, kind: "hook-failed", message: `hook "${cmd}" failed: ${e.message}` });
|
|
4632
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime: traceRuntime, kind: "hook-failed", message: `hook "${cmd}" failed: ${e.message}` });
|
|
4361
4633
|
break;
|
|
4362
4634
|
}
|
|
4363
4635
|
}
|
|
@@ -4374,7 +4646,16 @@ function createStageRunner(deps) {
|
|
|
4374
4646
|
}
|
|
4375
4647
|
if (denied) summary += "\n\n(note: one or more tool actions were blocked by a deny-only policy guard.)";
|
|
4376
4648
|
const failureClass = timedOut || stalled ? "harness" : result.failureClass;
|
|
4377
|
-
|
|
4649
|
+
if (isCheck && ref) writeCheckNote(ref, stageId, attempt, checkOutcomes);
|
|
4650
|
+
return finish(
|
|
4651
|
+
status,
|
|
4652
|
+
summary,
|
|
4653
|
+
result.sessionId ?? job.sessionId,
|
|
4654
|
+
result.costUsd,
|
|
4655
|
+
result.artifact,
|
|
4656
|
+
failureClass,
|
|
4657
|
+
result.verifications
|
|
4658
|
+
);
|
|
4378
4659
|
} finally {
|
|
4379
4660
|
inFlight.set(runId, Math.max(0, (inFlight.get(runId) ?? 1) - 1));
|
|
4380
4661
|
}
|
|
@@ -4461,6 +4742,7 @@ function createStageRunner(deps) {
|
|
|
4461
4742
|
var ENROLMENT_REJECTED_EXIT_CODE = 78;
|
|
4462
4743
|
var RECONNECT_BASE_MS = 500;
|
|
4463
4744
|
var RECONNECT_MAX_MS = 3e4;
|
|
4745
|
+
var NODE_CAPABILITIES = ["check"];
|
|
4464
4746
|
var PARK_POLL_MS = 6e4;
|
|
4465
4747
|
function classifyError(e) {
|
|
4466
4748
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -4644,6 +4926,12 @@ async function startEdgeNode(opts) {
|
|
|
4644
4926
|
os: osPlatform(),
|
|
4645
4927
|
arch: osArch(),
|
|
4646
4928
|
clientVersion: opts.clientVersion ?? "0.0.0",
|
|
4929
|
+
// Non-runtime job kinds this build can execute. The hub REFUSES to place a check job on a node
|
|
4930
|
+
// that does not advertise `check`, and that refusal is a safety gate rather than an optimisation:
|
|
4931
|
+
// `makeRunner()` defaults to Claude rather than erroring on an unknown job shape, so an old client
|
|
4932
|
+
// handed a check job would boot an unbounded write-access agent with the fallback instruction
|
|
4933
|
+
// "Begin the stage.", report ok, and let the run reach deliver green having run no checks.
|
|
4934
|
+
capabilities: NODE_CAPABILITIES,
|
|
4647
4935
|
// Advertise the resolved worktree base so the hub records each run's real worktree location in
|
|
4648
4936
|
// the projection instead of an advisory placeholder. Single-sourced from the git service so it
|
|
4649
4937
|
// always matches where worktrees actually land.
|
|
@@ -4976,7 +5264,7 @@ var PROBES = [
|
|
|
4976
5264
|
{ runtime: "claude-code", cmd: "claude" },
|
|
4977
5265
|
{ runtime: "pi", cmd: "pi" }
|
|
4978
5266
|
];
|
|
4979
|
-
var
|
|
5267
|
+
var DEFAULT_TIMEOUT_MS3 = 5e3;
|
|
4980
5268
|
var DEFAULT_ATTEMPTS = 2;
|
|
4981
5269
|
function probeOnce(cmd, timeoutMs) {
|
|
4982
5270
|
return new Promise((resolve3) => {
|
|
@@ -4998,14 +5286,14 @@ async function probe(cmd, timeoutMs, attempts) {
|
|
|
4998
5286
|
}
|
|
4999
5287
|
return void 0;
|
|
5000
5288
|
}
|
|
5001
|
-
async function probeRuntimeStatuses(timeoutMs =
|
|
5289
|
+
async function probeRuntimeStatuses(timeoutMs = DEFAULT_TIMEOUT_MS3, attempts = DEFAULT_ATTEMPTS) {
|
|
5002
5290
|
const versions = await Promise.all(PROBES.map((p) => probe(p.cmd, timeoutMs, attempts)));
|
|
5003
5291
|
return PROBES.map((p, i) => {
|
|
5004
5292
|
const version = versions[i];
|
|
5005
5293
|
return version === void 0 ? { runtime: p.runtime, cmd: p.cmd, installed: false } : { runtime: p.runtime, cmd: p.cmd, installed: true, version };
|
|
5006
5294
|
});
|
|
5007
5295
|
}
|
|
5008
|
-
async function detectRuntimes(timeoutMs =
|
|
5296
|
+
async function detectRuntimes(timeoutMs = DEFAULT_TIMEOUT_MS3, attempts = DEFAULT_ATTEMPTS) {
|
|
5009
5297
|
const statuses = await probeRuntimeStatuses(timeoutMs, attempts);
|
|
5010
5298
|
return statuses.filter((s) => s.installed).map((s) => s.runtime);
|
|
5011
5299
|
}
|
|
@@ -5611,8 +5899,8 @@ function usage(bin, command) {
|
|
|
5611
5899
|
}
|
|
5612
5900
|
|
|
5613
5901
|
// src/diagnose.ts
|
|
5614
|
-
import { existsSync as
|
|
5615
|
-
import { join as
|
|
5902
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
5903
|
+
import { join as join15 } from "path";
|
|
5616
5904
|
|
|
5617
5905
|
// src/ui.ts
|
|
5618
5906
|
import { styleText } from "util";
|
|
@@ -5727,7 +6015,7 @@ async function buildBundle(deps) {
|
|
|
5727
6015
|
if (deps.exists(deps.crashDir)) {
|
|
5728
6016
|
for (const name of deps.listDir(deps.crashDir).filter((f) => f.endsWith(".json")).sort()) {
|
|
5729
6017
|
try {
|
|
5730
|
-
crashes.push(JSON.parse(deps.readFile(
|
|
6018
|
+
crashes.push(JSON.parse(deps.readFile(join15(deps.crashDir, name))));
|
|
5731
6019
|
} catch (e) {
|
|
5732
6020
|
warnings.push(`could not read crash record ${name} (${e.message})`);
|
|
5733
6021
|
}
|
|
@@ -5784,18 +6072,18 @@ var defaultDiagnoseDeps = (paths, clientVersion, doctor) => ({
|
|
|
5784
6072
|
...paths,
|
|
5785
6073
|
clientVersion,
|
|
5786
6074
|
...doctor ? { doctor } : {},
|
|
5787
|
-
readFile: (p) =>
|
|
6075
|
+
readFile: (p) => readFileSync7(p, "utf8"),
|
|
5788
6076
|
listDir: (p) => readdirSync4(p),
|
|
5789
|
-
exists: (p) =>
|
|
6077
|
+
exists: (p) => existsSync9(p),
|
|
5790
6078
|
writeFile: (p, content) => {
|
|
5791
|
-
const dir =
|
|
5792
|
-
if (!
|
|
5793
|
-
|
|
6079
|
+
const dir = join15(p, "..");
|
|
6080
|
+
if (!existsSync9(dir)) mkdirSync9(dir, { recursive: true });
|
|
6081
|
+
writeFileSync9(p, content, { mode: 384 });
|
|
5794
6082
|
},
|
|
5795
6083
|
out
|
|
5796
6084
|
});
|
|
5797
6085
|
function defaultBundlePath(cwd, now) {
|
|
5798
|
-
return
|
|
6086
|
+
return join15(cwd, `dahrk-diagnose-${now.toISOString().replace(/[:.]/g, "-")}.json`);
|
|
5799
6087
|
}
|
|
5800
6088
|
|
|
5801
6089
|
// src/doctor.ts
|
|
@@ -5929,7 +6217,7 @@ async function runDoctor(inputs, deps = {}) {
|
|
|
5929
6217
|
}
|
|
5930
6218
|
|
|
5931
6219
|
// src/repo-add.ts
|
|
5932
|
-
import { createHash as
|
|
6220
|
+
import { createHash as createHash5 } from "crypto";
|
|
5933
6221
|
var stripRepoSuffix = (s) => s.replace(/\.git$/i, "").replace(/\/+$/, "");
|
|
5934
6222
|
function splitOwnerRepo(path) {
|
|
5935
6223
|
const parts = stripRepoSuffix(path).split("/").filter(Boolean);
|
|
@@ -5967,7 +6255,7 @@ var deriveRepoName = (remote) => remote.repo;
|
|
|
5967
6255
|
function deriveRepoId(gitUrl) {
|
|
5968
6256
|
const parsed = parseGitRemote(gitUrl);
|
|
5969
6257
|
const canonical2 = parsed ? `${parsed.host}/${parsed.owner}/${parsed.repo}`.toLowerCase() : gitUrl.trim().toLowerCase();
|
|
5970
|
-
const hash =
|
|
6258
|
+
const hash = createHash5("sha256").update(canonical2).digest("hex").slice(0, 12);
|
|
5971
6259
|
const slug = (parsed ? parsed.repo : "repo").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
5972
6260
|
return `${slug}-${hash}`;
|
|
5973
6261
|
}
|
|
@@ -6015,8 +6303,8 @@ async function registerRepo(deps, args) {
|
|
|
6015
6303
|
}
|
|
6016
6304
|
|
|
6017
6305
|
// src/lock.ts
|
|
6018
|
-
import { existsSync as
|
|
6019
|
-
import { dirname as
|
|
6306
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync8, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
6307
|
+
import { dirname as dirname7 } from "path";
|
|
6020
6308
|
function parseLock(content) {
|
|
6021
6309
|
if (!content) return void 0;
|
|
6022
6310
|
const pid = Number(content.trim());
|
|
@@ -6048,22 +6336,22 @@ var defaultLockDeps = (file) => ({
|
|
|
6048
6336
|
pid: process.pid,
|
|
6049
6337
|
readFile: (path) => {
|
|
6050
6338
|
try {
|
|
6051
|
-
return
|
|
6339
|
+
return readFileSync8(path, "utf8");
|
|
6052
6340
|
} catch {
|
|
6053
6341
|
return void 0;
|
|
6054
6342
|
}
|
|
6055
6343
|
},
|
|
6056
6344
|
writeFile: (path, content) => {
|
|
6057
|
-
if (!
|
|
6058
|
-
|
|
6345
|
+
if (!existsSync10(dirname7(path))) mkdirSync10(dirname7(path), { recursive: true, mode: 448 });
|
|
6346
|
+
writeFileSync10(path, content);
|
|
6059
6347
|
},
|
|
6060
6348
|
removeFile: (path) => rmSync7(path, { force: true }),
|
|
6061
6349
|
isAlive
|
|
6062
6350
|
});
|
|
6063
6351
|
|
|
6064
6352
|
// src/logs.ts
|
|
6065
|
-
import { spawn } from "child_process";
|
|
6066
|
-
import { copyFileSync, existsSync as
|
|
6353
|
+
import { spawn as spawn2 } from "child_process";
|
|
6354
|
+
import { copyFileSync, existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync4, truncateSync } from "fs";
|
|
6067
6355
|
var MAX_LOG_BYTES = 10 * 1024 * 1024;
|
|
6068
6356
|
function logsCommand(inputs) {
|
|
6069
6357
|
return [
|
|
@@ -6144,7 +6432,7 @@ async function runStructuredLogs(inputs, deps) {
|
|
|
6144
6432
|
}
|
|
6145
6433
|
function rotateIfLarge(file, maxBytes = MAX_LOG_BYTES) {
|
|
6146
6434
|
try {
|
|
6147
|
-
if (!
|
|
6435
|
+
if (!existsSync11(file) || statSync4(file).size <= maxBytes) return;
|
|
6148
6436
|
copyFileSync(file, `${file}.1`);
|
|
6149
6437
|
truncateSync(file, 0);
|
|
6150
6438
|
} catch {
|
|
@@ -6153,11 +6441,11 @@ function rotateIfLarge(file, maxBytes = MAX_LOG_BYTES) {
|
|
|
6153
6441
|
var defaultLogsDeps = (files, jsonlFile) => ({
|
|
6154
6442
|
files,
|
|
6155
6443
|
jsonlFile,
|
|
6156
|
-
fileExists: (path) =>
|
|
6157
|
-
readFile: (path) =>
|
|
6444
|
+
fileExists: (path) => existsSync11(path),
|
|
6445
|
+
readFile: (path) => readFileSync9(path, "utf8"),
|
|
6158
6446
|
run: (argv) => new Promise((resolve3) => {
|
|
6159
6447
|
const [cmd, ...args] = argv;
|
|
6160
|
-
const child =
|
|
6448
|
+
const child = spawn2(cmd, args, { stdio: "inherit" });
|
|
6161
6449
|
child.on("error", () => resolve3(1));
|
|
6162
6450
|
child.on("close", (code) => resolve3(code ?? 0));
|
|
6163
6451
|
}),
|
|
@@ -6165,13 +6453,13 @@ var defaultLogsDeps = (files, jsonlFile) => ({
|
|
|
6165
6453
|
});
|
|
6166
6454
|
|
|
6167
6455
|
// src/process-safety.ts
|
|
6168
|
-
import { mkdirSync as
|
|
6169
|
-
import { join as
|
|
6456
|
+
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
6457
|
+
import { join as join16 } from "path";
|
|
6170
6458
|
function writeCrashRecord(dir, record) {
|
|
6171
6459
|
try {
|
|
6172
|
-
|
|
6173
|
-
const file =
|
|
6174
|
-
|
|
6460
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
6461
|
+
const file = join16(dir, `${record.at.replace(/[:.]/g, "-")}.json`);
|
|
6462
|
+
writeFileSync11(file, `${JSON.stringify(record, null, 2)}
|
|
6175
6463
|
`, { mode: 384 });
|
|
6176
6464
|
return file;
|
|
6177
6465
|
} catch {
|
|
@@ -6224,11 +6512,11 @@ function installProcessSafetyNet(opts) {
|
|
|
6224
6512
|
}
|
|
6225
6513
|
|
|
6226
6514
|
// src/preflight.ts
|
|
6227
|
-
import { execFileSync as
|
|
6515
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
6228
6516
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6229
|
-
import { accessSync, constants as fsConstants, existsSync as
|
|
6517
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync12, readdirSync as readdirSync5, statfsSync as statfsSync2 } from "fs";
|
|
6230
6518
|
import { homedir as homedir4 } from "os";
|
|
6231
|
-
import { join as
|
|
6519
|
+
import { join as join17 } from "path";
|
|
6232
6520
|
var REPORT_BASE_URL = "https://app.dahrk.ai/r";
|
|
6233
6521
|
var LOW_DISK_BYTES = 512 * 1024 * 1024;
|
|
6234
6522
|
var PREFLIGHT_STAGES = [
|
|
@@ -6359,7 +6647,7 @@ var defaultDeps2 = () => ({
|
|
|
6359
6647
|
});
|
|
6360
6648
|
function commandPresent(cmd) {
|
|
6361
6649
|
try {
|
|
6362
|
-
|
|
6650
|
+
execFileSync7("sh", ["-c", `command -v ${cmd}`], { stdio: "ignore" });
|
|
6363
6651
|
return true;
|
|
6364
6652
|
} catch {
|
|
6365
6653
|
return false;
|
|
@@ -6367,12 +6655,12 @@ function commandPresent(cmd) {
|
|
|
6367
6655
|
}
|
|
6368
6656
|
function sshKeyPresent() {
|
|
6369
6657
|
try {
|
|
6370
|
-
const dir =
|
|
6371
|
-
if (
|
|
6658
|
+
const dir = join17(homedir4(), ".ssh");
|
|
6659
|
+
if (existsSync12(dir) && readdirSync5(dir).some((f) => f.endsWith(".pub"))) return true;
|
|
6372
6660
|
} catch {
|
|
6373
6661
|
}
|
|
6374
6662
|
try {
|
|
6375
|
-
|
|
6663
|
+
execFileSync7("ssh-add", ["-l"], { stdio: "ignore" });
|
|
6376
6664
|
return true;
|
|
6377
6665
|
} catch {
|
|
6378
6666
|
return false;
|
|
@@ -6387,12 +6675,12 @@ function writable(dir) {
|
|
|
6387
6675
|
}
|
|
6388
6676
|
}
|
|
6389
6677
|
function worktreeRoot(env) {
|
|
6390
|
-
return env.DAHRK_WORKTREES_DIR ??
|
|
6678
|
+
return env.DAHRK_WORKTREES_DIR ?? join17(env.DAHRK_STATE_DIR ?? join17(homedir4(), ".dahrk"), "worktrees");
|
|
6391
6679
|
}
|
|
6392
6680
|
function nearestExisting(dir) {
|
|
6393
6681
|
let cur = dir;
|
|
6394
|
-
while (!
|
|
6395
|
-
const parent =
|
|
6682
|
+
while (!existsSync12(cur)) {
|
|
6683
|
+
const parent = join17(cur, "..");
|
|
6396
6684
|
if (parent === cur) break;
|
|
6397
6685
|
cur = parent;
|
|
6398
6686
|
}
|
|
@@ -6407,7 +6695,7 @@ function freeDiskBytes(dir) {
|
|
|
6407
6695
|
}
|
|
6408
6696
|
}
|
|
6409
6697
|
function probeRepo(repoPath) {
|
|
6410
|
-
const git = (args) =>
|
|
6698
|
+
const git = (args) => execFileSync7("git", ["-C", repoPath, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
6411
6699
|
try {
|
|
6412
6700
|
if (git(["rev-parse", "--is-inside-work-tree"]) !== "true") {
|
|
6413
6701
|
return { path: repoPath, isGitRepo: false, headResolves: false, detail: "not inside a work tree" };
|
|
@@ -6453,15 +6741,15 @@ function gatherHostFacts(repoPath) {
|
|
|
6453
6741
|
}
|
|
6454
6742
|
|
|
6455
6743
|
// src/service.ts
|
|
6456
|
-
import { execFileSync as
|
|
6457
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
6744
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
6745
|
+
import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync11, realpathSync as realpathSync3, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
6458
6746
|
import { homedir as homedir6, platform as osPlatform3, userInfo } from "os";
|
|
6459
|
-
import { join as
|
|
6747
|
+
import { join as join19 } from "path";
|
|
6460
6748
|
|
|
6461
6749
|
// src/state.ts
|
|
6462
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
6750
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
6463
6751
|
import { homedir as homedir5 } from "os";
|
|
6464
|
-
import { join as
|
|
6752
|
+
import { join as join18 } from "path";
|
|
6465
6753
|
var RUNTIMES = ["claude-code", "codex", "pi"];
|
|
6466
6754
|
var isRuntime = (v) => RUNTIMES.includes(v);
|
|
6467
6755
|
var STRING_FIELDS = ["nodeId", "enrolToken", "name", "tenantId", "updateCheckedAt", "updateLatest"];
|
|
@@ -6469,37 +6757,37 @@ var isDesired = (v) => v === "running" || v === "stopped";
|
|
|
6469
6757
|
var FILE_MODE2 = 384;
|
|
6470
6758
|
var DIR_MODE2 = 448;
|
|
6471
6759
|
function stateDir(env) {
|
|
6472
|
-
return env.DAHRK_STATE_DIR ??
|
|
6760
|
+
return env.DAHRK_STATE_DIR ?? join18(homedir5(), ".dahrk");
|
|
6473
6761
|
}
|
|
6474
6762
|
function legacyStateDir(env) {
|
|
6475
|
-
return env.DAHRK_STATE_DIR ? void 0 :
|
|
6763
|
+
return env.DAHRK_STATE_DIR ? void 0 : join18(homedir5(), ".skakel");
|
|
6476
6764
|
}
|
|
6477
6765
|
function stateFile(env) {
|
|
6478
|
-
return
|
|
6766
|
+
return join18(stateDir(env), "node.json");
|
|
6479
6767
|
}
|
|
6480
6768
|
function logDir(env) {
|
|
6481
|
-
return
|
|
6769
|
+
return join18(stateDir(env), "logs");
|
|
6482
6770
|
}
|
|
6483
6771
|
function logFiles(env) {
|
|
6484
6772
|
const dir = logDir(env);
|
|
6485
|
-
return { out:
|
|
6773
|
+
return { out: join18(dir, "node.out.log"), err: join18(dir, "node.err.log") };
|
|
6486
6774
|
}
|
|
6487
6775
|
function jsonlLogFile(env) {
|
|
6488
|
-
return
|
|
6776
|
+
return join18(logDir(env), "node.jsonl");
|
|
6489
6777
|
}
|
|
6490
6778
|
function crashDir(env) {
|
|
6491
|
-
return
|
|
6779
|
+
return join18(logDir(env), "crashes");
|
|
6492
6780
|
}
|
|
6493
6781
|
function lockFile(env) {
|
|
6494
|
-
return
|
|
6782
|
+
return join18(stateDir(env), "node.pid");
|
|
6495
6783
|
}
|
|
6496
6784
|
function setDesired(env, desired) {
|
|
6497
6785
|
writeState(env, { desired });
|
|
6498
6786
|
}
|
|
6499
6787
|
function readState(file) {
|
|
6500
|
-
if (!
|
|
6788
|
+
if (!existsSync13(file)) return {};
|
|
6501
6789
|
try {
|
|
6502
|
-
const parsed = JSON.parse(
|
|
6790
|
+
const parsed = JSON.parse(readFileSync10(file, "utf8"));
|
|
6503
6791
|
const state = {};
|
|
6504
6792
|
for (const key of STRING_FIELDS) {
|
|
6505
6793
|
const value = parsed[key];
|
|
@@ -6519,9 +6807,9 @@ function writeState(env, patch) {
|
|
|
6519
6807
|
const dir = stateDir(env);
|
|
6520
6808
|
const file = stateFile(env);
|
|
6521
6809
|
try {
|
|
6522
|
-
|
|
6810
|
+
mkdirSync12(dir, { recursive: true, mode: DIR_MODE2 });
|
|
6523
6811
|
const next = { ...readState(file), ...patch };
|
|
6524
|
-
|
|
6812
|
+
writeFileSync12(file, `${JSON.stringify(next, null, 2)}
|
|
6525
6813
|
`, { mode: FILE_MODE2 });
|
|
6526
6814
|
chmodSync2(file, FILE_MODE2);
|
|
6527
6815
|
} catch (e) {
|
|
@@ -6602,9 +6890,9 @@ ${envEntries}
|
|
|
6602
6890
|
<key>ThrottleInterval</key>
|
|
6603
6891
|
<integer>10</integer>
|
|
6604
6892
|
<key>StandardOutPath</key>
|
|
6605
|
-
<string>${xmlEscape(
|
|
6893
|
+
<string>${xmlEscape(join19(inputs.logDir, "node.out.log"))}</string>
|
|
6606
6894
|
<key>StandardErrorPath</key>
|
|
6607
|
-
<string>${xmlEscape(
|
|
6895
|
+
<string>${xmlEscape(join19(inputs.logDir, "node.err.log"))}</string>
|
|
6608
6896
|
</dict>
|
|
6609
6897
|
</plist>
|
|
6610
6898
|
`;
|
|
@@ -6627,8 +6915,8 @@ Type=simple
|
|
|
6627
6915
|
ExecStart=${exec}
|
|
6628
6916
|
${envLines}
|
|
6629
6917
|
WorkingDirectory=${inputs.homeDir}
|
|
6630
|
-
StandardOutput=append:${
|
|
6631
|
-
StandardError=append:${
|
|
6918
|
+
StandardOutput=append:${join19(inputs.logDir, "node.out.log")}
|
|
6919
|
+
StandardError=append:${join19(inputs.logDir, "node.err.log")}
|
|
6632
6920
|
Restart=on-failure
|
|
6633
6921
|
RestartSec=3
|
|
6634
6922
|
RestartPreventExitStatus=78
|
|
@@ -6639,7 +6927,7 @@ WantedBy=default.target
|
|
|
6639
6927
|
}
|
|
6640
6928
|
function buildPlan(inputs) {
|
|
6641
6929
|
if (inputs.manager === "launchd") {
|
|
6642
|
-
const filePath2 =
|
|
6930
|
+
const filePath2 = join19(inputs.homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
6643
6931
|
return {
|
|
6644
6932
|
manager: "launchd",
|
|
6645
6933
|
label: LAUNCHD_LABEL,
|
|
@@ -6656,7 +6944,7 @@ function buildPlan(inputs) {
|
|
|
6656
6944
|
logHint: "dahrk logs -f"
|
|
6657
6945
|
};
|
|
6658
6946
|
}
|
|
6659
|
-
const filePath =
|
|
6947
|
+
const filePath = join19(inputs.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
6660
6948
|
const user = userInfo().username;
|
|
6661
6949
|
return {
|
|
6662
6950
|
manager: "systemd",
|
|
@@ -6686,7 +6974,7 @@ function unitIsCurrent(plan, onDisk) {
|
|
|
6686
6974
|
return onDisk === plan.content;
|
|
6687
6975
|
}
|
|
6688
6976
|
function unitPath(manager, homeDir) {
|
|
6689
|
-
return manager === "launchd" ?
|
|
6977
|
+
return manager === "launchd" ? join19(homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`) : join19(homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
6690
6978
|
}
|
|
6691
6979
|
function statusCommand(manager) {
|
|
6692
6980
|
return manager === "launchd" ? ["launchctl", "list", LAUNCHD_LABEL] : ["systemctl", "--user", "show", SYSTEMD_UNIT, "-p", "ActiveState", "-p", "MainPID"];
|
|
@@ -6983,29 +7271,29 @@ var defaultDeps3 = () => ({
|
|
|
6983
7271
|
// Snapshot the operator's PATH at install time so the daemon finds git + the runtime CLIs (Homebrew /
|
|
6984
7272
|
// npm-global bins) that a supervisor's minimal PATH would otherwise hide.
|
|
6985
7273
|
pathEnv: process.env.PATH,
|
|
6986
|
-
mkdirp: (dir) => void
|
|
7274
|
+
mkdirp: (dir) => void mkdirSync13(dir, { recursive: true }),
|
|
6987
7275
|
// The unit holds no secret any more, but it stays owner-only: it names paths on this host, and there is
|
|
6988
7276
|
// no reason for it to be world-readable. `writeFileSync`'s `mode` applies only when it CREATES the file,
|
|
6989
7277
|
// so chmod explicitly too - re-installing over a unit an older client left at 0644 must tighten it.
|
|
6990
7278
|
writeFile: (path, content) => {
|
|
6991
|
-
|
|
7279
|
+
writeFileSync13(path, content, { mode: UNIT_FILE_MODE });
|
|
6992
7280
|
chmodSync3(path, UNIT_FILE_MODE);
|
|
6993
7281
|
},
|
|
6994
7282
|
readFile: (path) => {
|
|
6995
7283
|
try {
|
|
6996
|
-
return
|
|
7284
|
+
return readFileSync11(path, "utf8");
|
|
6997
7285
|
} catch {
|
|
6998
7286
|
return void 0;
|
|
6999
7287
|
}
|
|
7000
7288
|
},
|
|
7001
7289
|
removeFile: (path) => rmSync8(path, { force: true }),
|
|
7002
|
-
fileExists: (path) =>
|
|
7290
|
+
fileExists: (path) => existsSync14(path),
|
|
7003
7291
|
// Captured, never inherited. A supervisor's stderr is ours to relay when it matters, not to leak
|
|
7004
7292
|
// unconditionally: `runCommands` decides who needs to read it.
|
|
7005
7293
|
run: (argv) => {
|
|
7006
7294
|
const [cmd, ...args] = argv;
|
|
7007
7295
|
try {
|
|
7008
|
-
const stdout =
|
|
7296
|
+
const stdout = execFileSync8(cmd, args, {
|
|
7009
7297
|
encoding: "utf8",
|
|
7010
7298
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7011
7299
|
});
|
|
@@ -7020,7 +7308,7 @@ var defaultDeps3 = () => ({
|
|
|
7020
7308
|
capture: (argv) => {
|
|
7021
7309
|
const [cmd, ...args] = argv;
|
|
7022
7310
|
try {
|
|
7023
|
-
const stdout =
|
|
7311
|
+
const stdout = execFileSync8(cmd, args, {
|
|
7024
7312
|
encoding: "utf8",
|
|
7025
7313
|
stdio: ["ignore", "pipe", "ignore"]
|
|
7026
7314
|
});
|
|
@@ -7034,7 +7322,7 @@ var defaultDeps3 = () => ({
|
|
|
7034
7322
|
});
|
|
7035
7323
|
|
|
7036
7324
|
// src/update.ts
|
|
7037
|
-
import { execFileSync as
|
|
7325
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
7038
7326
|
import { realpathSync as realpathSync4 } from "fs";
|
|
7039
7327
|
var LATEST_URL = "https://registry.npmjs.org/dahrk-node/latest";
|
|
7040
7328
|
var CHANNEL_COMMANDS = {
|
|
@@ -7166,7 +7454,7 @@ async function fetchLatestVersion(signal) {
|
|
|
7166
7454
|
function spawnUpgrade(argv) {
|
|
7167
7455
|
const [cmd, ...args] = argv;
|
|
7168
7456
|
try {
|
|
7169
|
-
const stdout =
|
|
7457
|
+
const stdout = execFileSync9(cmd, args, {
|
|
7170
7458
|
encoding: "utf8",
|
|
7171
7459
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7172
7460
|
});
|
|
@@ -7439,7 +7727,7 @@ async function runStatus(inputs, deps) {
|
|
|
7439
7727
|
}
|
|
7440
7728
|
|
|
7441
7729
|
// src/main.ts
|
|
7442
|
-
var CLIENT_VERSION = "0.1.
|
|
7730
|
+
var CLIENT_VERSION = "0.1.26";
|
|
7443
7731
|
var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
|
|
7444
7732
|
var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
7445
7733
|
var RUNTIMES2 = ["claude-code", "codex", "pi"];
|
|
@@ -7451,7 +7739,7 @@ function resolveNodeId(env, opts = {}) {
|
|
|
7451
7739
|
if (existing) return existing;
|
|
7452
7740
|
const legacy = legacyStateDir(env);
|
|
7453
7741
|
if (legacy) {
|
|
7454
|
-
const legacyId = readState(
|
|
7742
|
+
const legacyId = readState(join20(legacy, "node.json")).nodeId;
|
|
7455
7743
|
if (legacyId) return legacyId;
|
|
7456
7744
|
}
|
|
7457
7745
|
const nodeId = randomUUID3();
|
|
@@ -7719,11 +8007,11 @@ function statusDeps(env) {
|
|
|
7719
8007
|
}
|
|
7720
8008
|
return probeRuntimeStatuses();
|
|
7721
8009
|
},
|
|
7722
|
-
fileExists: (path) =>
|
|
8010
|
+
fileExists: (path) => existsSync15(path),
|
|
7723
8011
|
capture: (argv) => {
|
|
7724
8012
|
const [cmd, ...args] = argv;
|
|
7725
8013
|
try {
|
|
7726
|
-
const stdout =
|
|
8014
|
+
const stdout = execFileSync10(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
7727
8015
|
return { code: 0, stdout };
|
|
7728
8016
|
} catch (e) {
|
|
7729
8017
|
const status = e.status;
|
|
@@ -7748,7 +8036,7 @@ function statusDeps(env) {
|
|
|
7748
8036
|
}
|
|
7749
8037
|
function readFileOrUndefined(path) {
|
|
7750
8038
|
try {
|
|
7751
|
-
return
|
|
8039
|
+
return readFileSync12(path, "utf8");
|
|
7752
8040
|
} catch {
|
|
7753
8041
|
return void 0;
|
|
7754
8042
|
}
|