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.
Files changed (2) hide show
  1. package/dist/main.js +483 -195
  2. 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 execFileSync9 } from "child_process";
6
- import { existsSync as existsSync14, readFileSync as readFileSync11, realpathSync as realpathSync5 } from "fs";
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 join19 } from "path";
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 tail = truncated ? `
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)}${tail}
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 preamble = [ticket, guidance, gateFeedback, docs].filter(Boolean).join("\n\n");
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 existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, renameSync, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
2634
- import { dirname as dirname4, join as join10 } from "path";
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 (!existsSync5(file)) return [];
2879
+ if (!existsSync6(file)) return [];
2651
2880
  try {
2652
- const parsed = JSON.parse(readFileSync4(file, "utf8"));
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
- mkdirSync5(dirname4(file), { recursive: true, mode: DIR_MODE });
2663
- writeFileSync6(tmp, `${JSON.stringify(entries, null, 2)}
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 (existsSync5(tmp)) unlinkSync(tmp);
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 join10(stateDir2, "jobs.json");
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 existsSync6, mkdirSync as mkdirSync6, openSync, renameSync as renameSync2, rmSync as rmSync5, statSync as statSync2, writeSync } from "fs";
2802
- import { join as join11 } from "path";
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 = existsSync6(this.path) ? statSync2(this.path).size : 0;
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 (existsSync6(oldest)) rmSync5(oldest, { force: true });
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 (existsSync6(from)) renameSync2(from, `${this.path}.${i + 1}`);
3158
+ if (existsSync7(from)) renameSync2(from, `${this.path}.${i + 1}`);
2930
3159
  }
2931
- if (existsSync6(this.path)) renameSync2(this.path, `${this.path}.1`);
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
- mkdirSync6(opts.dir, { recursive: true, mode: 448 });
2992
- const file = new RotatingFile(join11(opts.dir, "node.jsonl"), opts.maxBytes ?? 10 * 1024 * 1024, opts.maxFiles ?? 5);
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 execFileSync5 } from "child_process";
3049
- import { createHash as createHash3 } from "crypto";
3050
- import { mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
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 join13, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
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 execFileSync4 } from "child_process";
3285
+ import { execFileSync as execFileSync5 } from "child_process";
3057
3286
 
3058
3287
  // ../../packages/edge/src/fs-roots.ts
3059
- import { execFileSync as execFileSync3 } from "child_process";
3060
- import { existsSync as existsSync7, realpathSync as realpathSync2 } from "fs";
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 dirname5, isAbsolute as isAbsolute2, join as join12, relative as relative2, resolve, sep as sep2 } from "path";
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 tail = [];
3075
- while (head !== dirname5(head)) {
3076
- if (existsSync7(head)) {
3303
+ const tail3 = [];
3304
+ while (head !== dirname6(head)) {
3305
+ if (existsSync8(head)) {
3077
3306
  try {
3078
- return join12(realpathSync2.native(head), ...tail.reverse());
3307
+ return join13(realpathSync2.native(head), ...tail3.reverse());
3079
3308
  } catch {
3080
3309
  return p;
3081
3310
  }
3082
3311
  }
3083
- tail.push(head.slice(dirname5(head).length + 1));
3084
- head = dirname5(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 = execFileSync3("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
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 = execFileSync3("pnpm", ["store", "path"], { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }).toString().trim();
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
- join12(home, ".gitconfig"),
3148
- join12(home, ".config"),
3149
- join12(home, ".npmrc"),
3150
- join12(home, ".cache"),
3151
- join12(home, "Library", "Caches"),
3152
- join12(home, "Library", "pnpm"),
3153
- join12(home, ".local", "share"),
3154
- join12(home, ".nvm"),
3155
- join12(home, ".volta"),
3156
- join12(home, ".asdf"),
3157
- join12(home, ".cargo"),
3158
- join12(home, ".rustup"),
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
- join12(home, ".ssh"),
3163
- join12(home, ".aws"),
3164
- join12(home, ".gnupg"),
3165
- join12(home, ".config", "gcloud"),
3166
- join12(home, "Library", "Keychains"),
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 execFileSync4("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreePath }).toString().trim();
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 digest = (value) => `sha256:${createHash3("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16)}`;
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 = join13(ref.scratchPath, "state.json");
4051
+ const statePath = join14(ref.scratchPath, "state.json");
3823
4052
  let state;
3824
4053
  try {
3825
- state = JSON.parse(readFileSync5(statePath, "utf8"));
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
- mkdirSync7(ref.scratchPath, { recursive: true });
3831
- writeFileSync7(statePath, JSON.stringify(state, null, 2));
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
- mkdirSync7(ref.scratchPath, { recursive: true });
3837
- writeFileSync7(join13(ref.scratchPath, "issue.md"), issueContext);
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 = join13(ref.scratchPath, "docs");
3845
- mkdirSync7(dir, { recursive: true });
4073
+ const dir = join14(ref.scratchPath, "docs");
4074
+ mkdirSync8(dir, { recursive: true });
3846
4075
  for (const doc of docs) {
3847
- writeFileSync7(join13(dir, `${attachedDocBasename2(doc)}.md`), doc.content);
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
- mkdirSync7(ref.scratchPath, { recursive: true });
3866
- writeFileSync7(join13(ref.scratchPath, "guidance.md"), renderGuidanceMarkdown(guidance));
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 = readFileSync5(path, "utf8");
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(join13(ref.worktreePath, SCRATCH_OUTPUT_DIR)).filter(
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 = readFileSync5(join13(ref.worktreePath, SCRATCH_OUTPUT_DIR, pick), "utf8");
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 execFileSync5("git", ["-C", ref.worktreePath, ...args], { encoding: "utf8" }).split("\n").map((s) => s.trim()).filter(Boolean);
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 = readFileSync5(join13(ref.worktreePath, rel), "utf8");
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, agentConfig } = job;
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 ?? join13(tmpdir5(), "dahrk", "scratch");
4053
- const worktreePath = join13(base, runId);
4054
- const scratchPath = join13(worktreePath, ".dahrk", "scratch");
4055
- mkdirSync7(scratchPath, { recursive: true });
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 (job.agentConfig.emitArtifact) {
4066
- const artifactDir = resolveWorktreeRelativePath(ref, job.agentConfig.emitArtifact);
4067
- const slash = job.agentConfig.emitArtifact.lastIndexOf("/");
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
- mkdirSync7(join13(ref.worktreePath, job.agentConfig.emitArtifact.slice(0, slash)), { recursive: true });
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: agentConfig.runtime,
4083
- model: agentConfig.model,
4323
+ runtime: traceRuntime,
4324
+ model: agentConfig?.model,
4084
4325
  sessionId: job.sessionId,
4085
- configDigest: digest(agentConfig),
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: agentConfig.runtime, event: "attempt-start" })
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(join13(writer.dir, "blobs"))) {
4099
- const bytes = readFileSync5(join13(writer.dir, "blobs", name));
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 = readFileSync5(join13(writer.dir, "trace.jsonl"));
4114
- const sha = createHash3("sha256").update(bytes).digest("hex");
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: agentConfig.runtime, event: "stage-exit", status: status2 })
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.emitArtifact !== void 0 || handedBackDoc !== void 0;
4152
- const resolved = status2 === "ok" && wantsArtifact ? resolveStageArtifact(ref, agentConfig.emitArtifact, handedBackDoc) : void 0;
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: agentConfig.runtime, event: "artifact", detail })
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: agentConfig.runtime, event: "provision", detail })
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: agentConfig.runtime, kind: "provision-failed", message: msg });
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: agentConfig.runtime, event: "policy-deny", detail: entry.reason });
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.runtime;
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.mcpServers;
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
- const runner = deps.makeRunner(runtime);
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: agentConfig,
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. Read through a narrow cast because the field is not in the published
4295
- // `@dahrk/contracts` (^0.4.0) yet; this line and `readAuthHint` are the only two seams, and
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.interaction === "interactive";
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
- execFileSync5("sh", ["-c", cmd], { cwd: ref.worktreePath, stdio: ["pipe", "pipe", "pipe"] });
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
- return finish(status, summary, result.sessionId ?? job.sessionId, result.costUsd, result.artifact, failureClass);
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 DEFAULT_TIMEOUT_MS = 5e3;
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 = DEFAULT_TIMEOUT_MS, attempts = DEFAULT_ATTEMPTS) {
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 = DEFAULT_TIMEOUT_MS, attempts = DEFAULT_ATTEMPTS) {
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 existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
5615
- import { join as join14 } from "path";
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(join14(deps.crashDir, name))));
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) => readFileSync6(p, "utf8"),
6075
+ readFile: (p) => readFileSync7(p, "utf8"),
5788
6076
  listDir: (p) => readdirSync4(p),
5789
- exists: (p) => existsSync8(p),
6077
+ exists: (p) => existsSync9(p),
5790
6078
  writeFile: (p, content) => {
5791
- const dir = join14(p, "..");
5792
- if (!existsSync8(dir)) mkdirSync8(dir, { recursive: true });
5793
- writeFileSync8(p, content, { mode: 384 });
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 join14(cwd, `dahrk-diagnose-${now.toISOString().replace(/[:.]/g, "-")}.json`);
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 createHash4 } from "crypto";
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 = createHash4("sha256").update(canonical2).digest("hex").slice(0, 12);
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 existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync7, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
6019
- import { dirname as dirname6 } from "path";
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 readFileSync7(path, "utf8");
6339
+ return readFileSync8(path, "utf8");
6052
6340
  } catch {
6053
6341
  return void 0;
6054
6342
  }
6055
6343
  },
6056
6344
  writeFile: (path, content) => {
6057
- if (!existsSync9(dirname6(path))) mkdirSync9(dirname6(path), { recursive: true, mode: 448 });
6058
- writeFileSync9(path, content);
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 existsSync10, readFileSync as readFileSync8, statSync as statSync4, truncateSync } from "fs";
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 (!existsSync10(file) || statSync4(file).size <= maxBytes) return;
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) => existsSync10(path),
6157
- readFile: (path) => readFileSync8(path, "utf8"),
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 = spawn(cmd, args, { stdio: "inherit" });
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 mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
6169
- import { join as join15 } from "path";
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
- mkdirSync10(dir, { recursive: true, mode: 448 });
6173
- const file = join15(dir, `${record.at.replace(/[:.]/g, "-")}.json`);
6174
- writeFileSync10(file, `${JSON.stringify(record, null, 2)}
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 execFileSync6 } from "child_process";
6515
+ import { execFileSync as execFileSync7 } from "child_process";
6228
6516
  import { randomUUID as randomUUID2 } from "crypto";
6229
- import { accessSync, constants as fsConstants, existsSync as existsSync11, readdirSync as readdirSync5, statfsSync as statfsSync2 } from "fs";
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 join16 } from "path";
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
- execFileSync6("sh", ["-c", `command -v ${cmd}`], { stdio: "ignore" });
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 = join16(homedir4(), ".ssh");
6371
- if (existsSync11(dir) && readdirSync5(dir).some((f) => f.endsWith(".pub"))) return true;
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
- execFileSync6("ssh-add", ["-l"], { stdio: "ignore" });
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 ?? join16(env.DAHRK_STATE_DIR ?? join16(homedir4(), ".dahrk"), "worktrees");
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 (!existsSync11(cur)) {
6395
- const parent = join16(cur, "..");
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) => execFileSync6("git", ["-C", repoPath, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
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 execFileSync7 } from "child_process";
6457
- import { chmodSync as chmodSync3, existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync10, realpathSync as realpathSync3, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "fs";
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 join18 } from "path";
6747
+ import { join as join19 } from "path";
6460
6748
 
6461
6749
  // src/state.ts
6462
- import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync11 } from "fs";
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 join17 } from "path";
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 ?? join17(homedir5(), ".dahrk");
6760
+ return env.DAHRK_STATE_DIR ?? join18(homedir5(), ".dahrk");
6473
6761
  }
6474
6762
  function legacyStateDir(env) {
6475
- return env.DAHRK_STATE_DIR ? void 0 : join17(homedir5(), ".skakel");
6763
+ return env.DAHRK_STATE_DIR ? void 0 : join18(homedir5(), ".skakel");
6476
6764
  }
6477
6765
  function stateFile(env) {
6478
- return join17(stateDir(env), "node.json");
6766
+ return join18(stateDir(env), "node.json");
6479
6767
  }
6480
6768
  function logDir(env) {
6481
- return join17(stateDir(env), "logs");
6769
+ return join18(stateDir(env), "logs");
6482
6770
  }
6483
6771
  function logFiles(env) {
6484
6772
  const dir = logDir(env);
6485
- return { out: join17(dir, "node.out.log"), err: join17(dir, "node.err.log") };
6773
+ return { out: join18(dir, "node.out.log"), err: join18(dir, "node.err.log") };
6486
6774
  }
6487
6775
  function jsonlLogFile(env) {
6488
- return join17(logDir(env), "node.jsonl");
6776
+ return join18(logDir(env), "node.jsonl");
6489
6777
  }
6490
6778
  function crashDir(env) {
6491
- return join17(logDir(env), "crashes");
6779
+ return join18(logDir(env), "crashes");
6492
6780
  }
6493
6781
  function lockFile(env) {
6494
- return join17(stateDir(env), "node.pid");
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 (!existsSync12(file)) return {};
6788
+ if (!existsSync13(file)) return {};
6501
6789
  try {
6502
- const parsed = JSON.parse(readFileSync9(file, "utf8"));
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
- mkdirSync11(dir, { recursive: true, mode: DIR_MODE2 });
6810
+ mkdirSync12(dir, { recursive: true, mode: DIR_MODE2 });
6523
6811
  const next = { ...readState(file), ...patch };
6524
- writeFileSync11(file, `${JSON.stringify(next, null, 2)}
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(join18(inputs.logDir, "node.out.log"))}</string>
6893
+ <string>${xmlEscape(join19(inputs.logDir, "node.out.log"))}</string>
6606
6894
  <key>StandardErrorPath</key>
6607
- <string>${xmlEscape(join18(inputs.logDir, "node.err.log"))}</string>
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:${join18(inputs.logDir, "node.out.log")}
6631
- StandardError=append:${join18(inputs.logDir, "node.err.log")}
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 = join18(inputs.homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
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 = join18(inputs.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
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" ? join18(homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`) : join18(homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
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 mkdirSync12(dir, { recursive: true }),
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
- writeFileSync12(path, content, { mode: UNIT_FILE_MODE });
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 readFileSync10(path, "utf8");
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) => existsSync13(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 = execFileSync7(cmd, args, {
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 = execFileSync7(cmd, args, {
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 execFileSync8 } from "child_process";
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 = execFileSync8(cmd, args, {
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.24";
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(join19(legacy, "node.json")).nodeId;
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) => existsSync14(path),
8010
+ fileExists: (path) => existsSync15(path),
7723
8011
  capture: (argv) => {
7724
8012
  const [cmd, ...args] = argv;
7725
8013
  try {
7726
- const stdout = execFileSync9(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
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 readFileSync11(path, "utf8");
8039
+ return readFileSync12(path, "utf8");
7752
8040
  } catch {
7753
8041
  return void 0;
7754
8042
  }