dahrk-node 0.1.25 → 0.1.27
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 +289 -55
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -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.";
|
|
@@ -1349,14 +1362,16 @@ async function defaultCreatePiSession(ctx) {
|
|
|
1349
1362
|
applyApiKeyAuth(hint2, ctx.runtimeEnv, authStorage);
|
|
1350
1363
|
const modelRegistry = ModelRegistry.create(authStorage, join4(configDir, "models.json"));
|
|
1351
1364
|
let model;
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1365
|
+
try {
|
|
1366
|
+
model = selectStageModel({
|
|
1367
|
+
stageModel: ctx.config.model,
|
|
1368
|
+
profileDefaultModel: hint2?.defaultModel,
|
|
1369
|
+
resolve: (id) => resolveCliModel({ cliModel: id, modelRegistry }),
|
|
1370
|
+
available: () => modelRegistry.getAvailable()
|
|
1371
|
+
});
|
|
1372
|
+
} catch (err) {
|
|
1373
|
+
cleanupStageConfigDir(configDir);
|
|
1374
|
+
throw err;
|
|
1360
1375
|
}
|
|
1361
1376
|
const stageComplete = defineTool({
|
|
1362
1377
|
name: PI_STAGE_COMPLETE_TOOL,
|
|
@@ -1452,6 +1467,20 @@ function modelFamily(id) {
|
|
|
1452
1467
|
const last = id.split(".").pop() ?? id;
|
|
1453
1468
|
return last.replace(/-v\d+:\d+$/, "").toLowerCase();
|
|
1454
1469
|
}
|
|
1470
|
+
function selectStageModel(args) {
|
|
1471
|
+
const requested = args.stageModel ?? args.profileDefaultModel;
|
|
1472
|
+
if (!requested) return void 0;
|
|
1473
|
+
const resolved = args.resolve(requested);
|
|
1474
|
+
if (resolved?.error || !resolved?.model) {
|
|
1475
|
+
const available = args.available() ?? [];
|
|
1476
|
+
const sample = available.slice(0, 12).map((m) => `${m.provider}/${m.id}`);
|
|
1477
|
+
const detail = resolved?.error ? resolved.error : "the model id is not recognised";
|
|
1478
|
+
throw new Error(
|
|
1479
|
+
`Pi cannot resolve the model '${requested}': ${detail}. ` + (available.length ? `Models this stage can authenticate to (${available.length}): ${sample.join(", ")}${available.length > sample.length ? ", ..." : ""}.` : "This stage has no authenticated models at all; check the auth profile bound to its node pool.") + " If the id is newer than this node's pinned Pi, upgrade dahrk-node."
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
return pickAuthedModel(resolved.model, args.available(), args.stageModel ? args.profileDefaultModel : void 0);
|
|
1483
|
+
}
|
|
1455
1484
|
function pickAuthedModel(resolved, available, fallbackModel) {
|
|
1456
1485
|
if (!resolved || !available?.length) return resolved;
|
|
1457
1486
|
const providers = new Set(available.map((m) => m.provider));
|
|
@@ -2617,6 +2646,169 @@ function runRepoSetup(opts) {
|
|
|
2617
2646
|
}
|
|
2618
2647
|
}
|
|
2619
2648
|
|
|
2649
|
+
// ../../packages/executor-worktree/src/check-runner.ts
|
|
2650
|
+
import { spawn } from "child_process";
|
|
2651
|
+
var DEFAULT_TIMEOUT_MS2 = 6e5;
|
|
2652
|
+
var OUTPUT_CAP2 = 16384;
|
|
2653
|
+
function tail2(output) {
|
|
2654
|
+
return output.length > OUTPUT_CAP2 ? output.slice(output.length - OUTPUT_CAP2) : output;
|
|
2655
|
+
}
|
|
2656
|
+
async function runOne(check, cwd, startedAt) {
|
|
2657
|
+
const timeoutMs = (check.timeoutSeconds ?? DEFAULT_TIMEOUT_MS2 / 1e3) * 1e3;
|
|
2658
|
+
return await new Promise((resolve3) => {
|
|
2659
|
+
let chunks = "";
|
|
2660
|
+
let timedOut = false;
|
|
2661
|
+
let settled = false;
|
|
2662
|
+
const child = spawn("sh", ["-c", check.command], { cwd, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
2663
|
+
const append = (buf) => {
|
|
2664
|
+
chunks = tail2(chunks + buf.toString("utf8"));
|
|
2665
|
+
};
|
|
2666
|
+
child.stdout?.on("data", append);
|
|
2667
|
+
child.stderr?.on("data", append);
|
|
2668
|
+
const timer = setTimeout(() => {
|
|
2669
|
+
timedOut = true;
|
|
2670
|
+
child.kill("SIGKILL");
|
|
2671
|
+
}, timeoutMs);
|
|
2672
|
+
const settle = (exitCode) => {
|
|
2673
|
+
if (settled) return;
|
|
2674
|
+
settled = true;
|
|
2675
|
+
clearTimeout(timer);
|
|
2676
|
+
resolve3({
|
|
2677
|
+
name: check.name,
|
|
2678
|
+
command: check.command,
|
|
2679
|
+
exitCode,
|
|
2680
|
+
output: chunks,
|
|
2681
|
+
status: exitCode === 0 ? "passed" : "failed",
|
|
2682
|
+
durationMs: Date.now() - startedAt,
|
|
2683
|
+
timedOut
|
|
2684
|
+
});
|
|
2685
|
+
};
|
|
2686
|
+
child.on("close", (code) => settle(code));
|
|
2687
|
+
child.on("error", (err) => {
|
|
2688
|
+
chunks = tail2(`${chunks}
|
|
2689
|
+
check could not start: ${err.message}`);
|
|
2690
|
+
settle(null);
|
|
2691
|
+
});
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
function summariseChecks(outcomes) {
|
|
2695
|
+
const failed = outcomes.filter((o) => o.status === "failed");
|
|
2696
|
+
const skipped = outcomes.filter((o) => o.status === "skipped");
|
|
2697
|
+
if (outcomes.length === 0) return "no checks declared";
|
|
2698
|
+
const suffix = skipped.length > 0 ? `; ${skipped.length} not run` : "";
|
|
2699
|
+
if (failed.length === 0) return `all ${outcomes.length} checks passed${suffix}`;
|
|
2700
|
+
return `${failed.length} of ${outcomes.length} checks failed: ${failed.map((o) => o.name).join(", ")}${suffix}`;
|
|
2701
|
+
}
|
|
2702
|
+
function createCheckRunner(checks, onOutcomes) {
|
|
2703
|
+
let cancelled = false;
|
|
2704
|
+
let lastSummary = "";
|
|
2705
|
+
return {
|
|
2706
|
+
// A check stage runs no agent, so claiming an agent runtime here would corrupt the trace record
|
|
2707
|
+
// the optimiser and the observability CLI both read.
|
|
2708
|
+
runtime: "check",
|
|
2709
|
+
async runBatch(ctx, onTrace) {
|
|
2710
|
+
const cwd = ctx.workspace.worktreePath;
|
|
2711
|
+
const outcomes = [];
|
|
2712
|
+
let seq = 0;
|
|
2713
|
+
for (const check of checks) {
|
|
2714
|
+
if (cancelled) {
|
|
2715
|
+
outcomes.push({
|
|
2716
|
+
name: check.name,
|
|
2717
|
+
command: check.command,
|
|
2718
|
+
exitCode: null,
|
|
2719
|
+
output: "",
|
|
2720
|
+
status: "skipped",
|
|
2721
|
+
durationMs: 0,
|
|
2722
|
+
timedOut: false
|
|
2723
|
+
});
|
|
2724
|
+
continue;
|
|
2725
|
+
}
|
|
2726
|
+
onTrace({
|
|
2727
|
+
seq: seq++,
|
|
2728
|
+
runtime: "check",
|
|
2729
|
+
type: "action",
|
|
2730
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2731
|
+
tool: "check",
|
|
2732
|
+
toolUseId: check.name,
|
|
2733
|
+
input: { name: check.name, command: check.command }
|
|
2734
|
+
});
|
|
2735
|
+
const outcome = await runOne(check, cwd, Date.now());
|
|
2736
|
+
outcomes.push(outcome);
|
|
2737
|
+
onTrace({
|
|
2738
|
+
seq: seq++,
|
|
2739
|
+
runtime: "check",
|
|
2740
|
+
type: "observation",
|
|
2741
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2742
|
+
toolUseId: check.name,
|
|
2743
|
+
isError: outcome.status === "failed",
|
|
2744
|
+
output: {
|
|
2745
|
+
name: outcome.name,
|
|
2746
|
+
exitCode: outcome.exitCode,
|
|
2747
|
+
timedOut: outcome.timedOut,
|
|
2748
|
+
durationMs: outcome.durationMs,
|
|
2749
|
+
output: outcome.output
|
|
2750
|
+
}
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
lastSummary = summariseChecks(outcomes);
|
|
2754
|
+
onTrace({
|
|
2755
|
+
seq: seq++,
|
|
2756
|
+
runtime: "check",
|
|
2757
|
+
type: "response",
|
|
2758
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2759
|
+
text: lastSummary
|
|
2760
|
+
});
|
|
2761
|
+
onOutcomes?.(outcomes);
|
|
2762
|
+
const verifications = outcomes.map((o) => ({ name: o.name, status: o.status }));
|
|
2763
|
+
const anyFailed = outcomes.some((o) => o.status === "failed");
|
|
2764
|
+
return {
|
|
2765
|
+
status: cancelled ? "timeout" : anyFailed ? "fail" : "ok",
|
|
2766
|
+
verifications
|
|
2767
|
+
};
|
|
2768
|
+
},
|
|
2769
|
+
async runInteractive() {
|
|
2770
|
+
throw new Error("a check stage cannot be interactive");
|
|
2771
|
+
},
|
|
2772
|
+
/** Deterministic, and free: no model is asked to describe a lint run. */
|
|
2773
|
+
async summarise() {
|
|
2774
|
+
return lastSummary || summariseChecks([]);
|
|
2775
|
+
},
|
|
2776
|
+
async cancel() {
|
|
2777
|
+
cancelled = true;
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
var CHECK_NOTE_CAP_BYTES = 64 * 1024;
|
|
2782
|
+
var safeStageSegment = (stageId) => stageId.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
2783
|
+
function renderCheckNote(stageId, attempt, outcomes) {
|
|
2784
|
+
const failed = outcomes.filter((o) => o.status === "failed");
|
|
2785
|
+
const head = `# checks/${stageId} (attempt ${attempt}) - ${failed.length === 0 ? "PASSED" : "FAILED"}
|
|
2786
|
+
|
|
2787
|
+
${summariseChecks(outcomes)}
|
|
2788
|
+
`;
|
|
2789
|
+
const ordered = [...outcomes].sort((a, b) => Number(b.status === "failed") - Number(a.status === "failed"));
|
|
2790
|
+
let body = "";
|
|
2791
|
+
let budget = CHECK_NOTE_CAP_BYTES - head.length;
|
|
2792
|
+
for (const o of ordered) {
|
|
2793
|
+
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}`;
|
|
2794
|
+
const header = `
|
|
2795
|
+
## ${o.name} - ${o.status} (${verdict2})
|
|
2796
|
+
|
|
2797
|
+
$ ${o.command}
|
|
2798
|
+
`;
|
|
2799
|
+
const output = o.output.trim();
|
|
2800
|
+
const includeOutput = output.length > 0 && (o.status === "failed" || header.length + output.length < budget);
|
|
2801
|
+
const chunk = includeOutput ? `${header}
|
|
2802
|
+
\`\`\`
|
|
2803
|
+
${output}
|
|
2804
|
+
\`\`\`
|
|
2805
|
+
` : header;
|
|
2806
|
+
body += chunk;
|
|
2807
|
+
budget -= chunk.length;
|
|
2808
|
+
}
|
|
2809
|
+
return head + body;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2620
2812
|
// ../../packages/executor-worktree/src/index.ts
|
|
2621
2813
|
function makeRunner(runtime) {
|
|
2622
2814
|
if ((process.env.DAHRK_RUNNER ?? process.env.SKAKEL_RUNNER ?? "real") === "mock") return createMockRunner(runtime);
|
|
@@ -3103,7 +3295,7 @@ import { createHash as createHash4 } from "crypto";
|
|
|
3103
3295
|
import { mkdirSync as mkdirSync8, readdirSync as readdirSync3, readFileSync as readFileSync6, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
3104
3296
|
import { tmpdir as tmpdir5 } from "os";
|
|
3105
3297
|
import { isAbsolute as isAbsolute3, join as join14, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
|
|
3106
|
-
import { attachedDocBasename as attachedDocBasename2 } from "@dahrk/contracts";
|
|
3298
|
+
import { attachedDocBasename as attachedDocBasename2, isCheckJob } from "@dahrk/contracts";
|
|
3107
3299
|
|
|
3108
3300
|
// ../../packages/edge/src/builtins.ts
|
|
3109
3301
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
@@ -3124,16 +3316,16 @@ function expandPath(raw, cwd) {
|
|
|
3124
3316
|
}
|
|
3125
3317
|
function realish(p) {
|
|
3126
3318
|
let head = p;
|
|
3127
|
-
const
|
|
3319
|
+
const tail3 = [];
|
|
3128
3320
|
while (head !== dirname6(head)) {
|
|
3129
3321
|
if (existsSync8(head)) {
|
|
3130
3322
|
try {
|
|
3131
|
-
return join13(realpathSync2.native(head), ...
|
|
3323
|
+
return join13(realpathSync2.native(head), ...tail3.reverse());
|
|
3132
3324
|
} catch {
|
|
3133
3325
|
return p;
|
|
3134
3326
|
}
|
|
3135
3327
|
}
|
|
3136
|
-
|
|
3328
|
+
tail3.push(head.slice(dirname6(head).length + 1));
|
|
3137
3329
|
head = dirname6(head);
|
|
3138
3330
|
}
|
|
3139
3331
|
return p;
|
|
@@ -3920,6 +4112,15 @@ function writeGuidance(ref, guidance) {
|
|
|
3920
4112
|
} catch {
|
|
3921
4113
|
}
|
|
3922
4114
|
}
|
|
4115
|
+
function writeCheckNote(ref, stageId, attempt, outcomes) {
|
|
4116
|
+
if (outcomes.length === 0) return;
|
|
4117
|
+
try {
|
|
4118
|
+
const dir = join14(ref.scratchPath, "checks");
|
|
4119
|
+
mkdirSync8(dir, { recursive: true });
|
|
4120
|
+
writeFileSync8(join14(dir, `${safeStageSegment(stageId)}-${attempt}.md`), renderCheckNote(stageId, attempt, outcomes));
|
|
4121
|
+
} catch {
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
3923
4124
|
var ARTIFACT_CAP_BYTES = 64 * 1024;
|
|
3924
4125
|
var SCRATCH_OUTPUT_DIR = ".dahrk/scratch/output";
|
|
3925
4126
|
function capContent(raw) {
|
|
@@ -4068,7 +4269,10 @@ function createStageRunner(deps) {
|
|
|
4068
4269
|
return reaper.reap(reaperDryRun ? { dryRun: true } : {});
|
|
4069
4270
|
},
|
|
4070
4271
|
async runJob(job) {
|
|
4071
|
-
const { stageId, jobId, runId
|
|
4272
|
+
const { stageId, jobId, runId } = job;
|
|
4273
|
+
const isCheck = isCheckJob(job);
|
|
4274
|
+
const agentConfig = job.agentConfig;
|
|
4275
|
+
const traceRuntime = isCheck ? "check" : agentConfig.runtime;
|
|
4072
4276
|
const attempt = attemptOf(jobId);
|
|
4073
4277
|
if (deps.tenantId && job.tenantId !== deps.tenantId) {
|
|
4074
4278
|
return {
|
|
@@ -4115,12 +4319,12 @@ function createStageRunner(deps) {
|
|
|
4115
4319
|
writeIssueContext(ref, job.issueContext);
|
|
4116
4320
|
writeGuidance(ref, job.guidance);
|
|
4117
4321
|
writeAttachedDocuments(ref, job.attachedDocuments);
|
|
4118
|
-
if (
|
|
4119
|
-
const artifactDir = resolveWorktreeRelativePath(ref,
|
|
4120
|
-
const slash =
|
|
4322
|
+
if (agentConfig?.emitArtifact) {
|
|
4323
|
+
const artifactDir = resolveWorktreeRelativePath(ref, agentConfig.emitArtifact);
|
|
4324
|
+
const slash = agentConfig.emitArtifact.lastIndexOf("/");
|
|
4121
4325
|
if (artifactDir && slash > 0) {
|
|
4122
4326
|
try {
|
|
4123
|
-
mkdirSync8(join14(ref.worktreePath,
|
|
4327
|
+
mkdirSync8(join14(ref.worktreePath, agentConfig.emitArtifact.slice(0, slash)), { recursive: true });
|
|
4124
4328
|
} catch {
|
|
4125
4329
|
}
|
|
4126
4330
|
}
|
|
@@ -4132,16 +4336,16 @@ function createStageRunner(deps) {
|
|
|
4132
4336
|
stageId,
|
|
4133
4337
|
jobId,
|
|
4134
4338
|
attempt,
|
|
4135
|
-
runtime:
|
|
4136
|
-
model: agentConfig
|
|
4339
|
+
runtime: traceRuntime,
|
|
4340
|
+
model: agentConfig?.model,
|
|
4137
4341
|
sessionId: job.sessionId,
|
|
4138
|
-
configDigest: digest2(agentConfig),
|
|
4342
|
+
configDigest: digest2(isCheck ? job.checks : agentConfig),
|
|
4139
4343
|
startedAt: nowIso2()
|
|
4140
4344
|
};
|
|
4141
4345
|
const writer = createTraceWriter(ref.scratchPath, meta);
|
|
4142
4346
|
const streamEvent = (e) => deps.trace?.event({ runId, stageId, attempt, tenantId: job.tenantId, event: e });
|
|
4143
4347
|
streamEvent(
|
|
4144
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4348
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "attempt-start" })
|
|
4145
4349
|
);
|
|
4146
4350
|
const shipFinalTrace = async (finalMeta) => {
|
|
4147
4351
|
const sink = deps.trace;
|
|
@@ -4178,13 +4382,13 @@ function createStageRunner(deps) {
|
|
|
4178
4382
|
}
|
|
4179
4383
|
sink.finalised({ ...base, meta: finalMeta, eventCount: writer.count(), ...archiveKey ? { archiveKey } : {} });
|
|
4180
4384
|
};
|
|
4181
|
-
const finish = async (status2, summary2, sessionId, costUsd, handedBackDoc, failureClass2) => {
|
|
4385
|
+
const finish = async (status2, summary2, sessionId, costUsd, handedBackDoc, failureClass2, verifications) => {
|
|
4182
4386
|
active.delete(jobId);
|
|
4183
4387
|
turnQueues.delete(jobId);
|
|
4184
4388
|
await gateway?.stop().catch((e) => log.warn({ err: e, jobId }, "mcp gateway: stop failed"));
|
|
4185
4389
|
gateway = void 0;
|
|
4186
4390
|
streamEvent(
|
|
4187
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4391
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "stage-exit", status: status2 })
|
|
4188
4392
|
);
|
|
4189
4393
|
const endedAt = nowIso2();
|
|
4190
4394
|
writer.finalise({ status: status2, endedAt, ...sessionId ? { sessionId } : {} });
|
|
@@ -4201,12 +4405,12 @@ function createStageRunner(deps) {
|
|
|
4201
4405
|
);
|
|
4202
4406
|
lastUsed.set(runId, Date.now());
|
|
4203
4407
|
await applyRetention(runId).catch((e) => log.warn({ err: e, runId }, "retention: pass failed"));
|
|
4204
|
-
const wantsArtifact = agentConfig
|
|
4205
|
-
const resolved = status2 === "ok" && wantsArtifact ? resolveStageArtifact(ref, agentConfig
|
|
4408
|
+
const wantsArtifact = agentConfig?.emitArtifact !== void 0 || handedBackDoc !== void 0;
|
|
4409
|
+
const resolved = status2 === "ok" && wantsArtifact ? resolveStageArtifact(ref, agentConfig?.emitArtifact, handedBackDoc) : void 0;
|
|
4206
4410
|
if (status2 === "ok" && wantsArtifact) {
|
|
4207
4411
|
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)";
|
|
4208
4412
|
streamEvent(
|
|
4209
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4413
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "artifact", detail })
|
|
4210
4414
|
);
|
|
4211
4415
|
}
|
|
4212
4416
|
return {
|
|
@@ -4216,7 +4420,12 @@ function createStageRunner(deps) {
|
|
|
4216
4420
|
...sessionId ? { sessionId } : {},
|
|
4217
4421
|
...costUsd !== void 0 ? { costUsd } : {},
|
|
4218
4422
|
...resolved ? { artifact: resolved.artifact } : {},
|
|
4219
|
-
...failureClass2 ? { failureClass: failureClass2 } : {}
|
|
4423
|
+
...failureClass2 ? { failureClass: failureClass2 } : {},
|
|
4424
|
+
// DHK-666: `JobResult.verifications` shipped with a contract, a projection fold, a Card
|
|
4425
|
+
// renderer and tests - but this object never forwarded it, which is the entire reason the
|
|
4426
|
+
// field has had no producer. Forwarded for EVERY stage, not just check jobs, so a hook or
|
|
4427
|
+
// an agent-run gate can populate it too.
|
|
4428
|
+
...verifications?.length ? { verifications } : {}
|
|
4220
4429
|
};
|
|
4221
4430
|
};
|
|
4222
4431
|
if (deps.packCache && job.provision && job.provision.length > 0) {
|
|
@@ -4229,30 +4438,30 @@ function createStageRunner(deps) {
|
|
|
4229
4438
|
});
|
|
4230
4439
|
const detail = `provision: ${overlay.written.length} written, ${overlay.skippedRepoLocal.length} repo-local, ${overlay.warnings.length} warning(s)`;
|
|
4231
4440
|
streamEvent(
|
|
4232
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4441
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "provision", detail })
|
|
4233
4442
|
);
|
|
4234
4443
|
const noteText = overlay.warnings.length > 0 ? `${detail}; ${overlay.warnings.join("; ")}` : detail;
|
|
4235
4444
|
deps.sendProgress({ jobId, kind: "observation", ts: nowIso2(), text: noteText });
|
|
4236
4445
|
} catch (e) {
|
|
4237
4446
|
const msg = `component provisioning failed: ${e.message}`;
|
|
4238
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime:
|
|
4447
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime: traceRuntime, kind: "provision-failed", message: msg });
|
|
4239
4448
|
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: msg });
|
|
4240
4449
|
return finish("fail", `${stageId}: ${msg}`, job.sessionId);
|
|
4241
4450
|
}
|
|
4242
4451
|
}
|
|
4243
|
-
const setup = job.setup;
|
|
4452
|
+
const setup = job.workspaceRef?.setup;
|
|
4244
4453
|
if (setup?.command && ref) {
|
|
4245
4454
|
const outcome = runRepoSetup({ worktreePath: ref.worktreePath, command: setup.command });
|
|
4246
4455
|
if (outcome.status === "failed") {
|
|
4247
4456
|
const msg = `repo setup failed (exit ${outcome.exitCode ?? "null"})`;
|
|
4248
4457
|
const detail2 = outcome.output ? `${msg}: ${outcome.output}` : msg;
|
|
4249
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime:
|
|
4458
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime: traceRuntime, kind: "setup-failed", message: detail2 });
|
|
4250
4459
|
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: detail2 });
|
|
4251
4460
|
return finish("fail", `${stageId}: ${msg}`, job.sessionId, void 0, void 0, "harness");
|
|
4252
4461
|
}
|
|
4253
4462
|
const detail = outcome.status === "cached" ? "setup: cached (already installed)" : `setup: ran (exit 0, ${outcome.output.length} bytes)`;
|
|
4254
4463
|
streamEvent(
|
|
4255
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4464
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "setup", detail })
|
|
4256
4465
|
);
|
|
4257
4466
|
if (outcome.status === "ran") {
|
|
4258
4467
|
deps.sendProgress({ jobId, kind: "observation", ts: nowIso2(), text: outcome.output || detail });
|
|
@@ -4275,14 +4484,14 @@ function createStageRunner(deps) {
|
|
|
4275
4484
|
const rules = [...jobRules, ...deps.rules];
|
|
4276
4485
|
const entry = evaluatePolicies({ kind: "stage-entry", stageId }, rules);
|
|
4277
4486
|
if (entry.verdict === "deny") {
|
|
4278
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime:
|
|
4487
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "policy-deny", detail: entry.reason });
|
|
4279
4488
|
return finish("fail", `${stageId}: denied at stage entry (${entry.policy})`, job.sessionId);
|
|
4280
4489
|
}
|
|
4281
4490
|
let denied = false;
|
|
4282
4491
|
const surfacedDenyReasons = /* @__PURE__ */ new Set();
|
|
4283
4492
|
let escapedUnblocked = false;
|
|
4284
4493
|
const authorisedActions = [];
|
|
4285
|
-
const runtime = agentConfig
|
|
4494
|
+
const runtime = agentConfig?.runtime;
|
|
4286
4495
|
const actionKey = (tool3, input) => {
|
|
4287
4496
|
try {
|
|
4288
4497
|
return `${tool3}\0${JSON.stringify(input)}`;
|
|
@@ -4295,9 +4504,9 @@ function createStageRunner(deps) {
|
|
|
4295
4504
|
denied = true;
|
|
4296
4505
|
const reason = policyReason(verdict2);
|
|
4297
4506
|
if (toolUseId) {
|
|
4298
|
-
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "observation", runtime, toolUseId, isError: true, output: { error: reason } }));
|
|
4507
|
+
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "observation", runtime: traceRuntime, toolUseId, isError: true, output: { error: reason } }));
|
|
4299
4508
|
}
|
|
4300
|
-
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime, event: "policy-deny", detail: reason }));
|
|
4509
|
+
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime: traceRuntime, event: "policy-deny", detail: reason }));
|
|
4301
4510
|
const surfaceKey = `${verdict2.policy}\0${reason}`;
|
|
4302
4511
|
if (!surfacedDenyReasons.has(surfaceKey)) {
|
|
4303
4512
|
surfacedDenyReasons.add(surfaceKey);
|
|
@@ -4338,19 +4547,28 @@ function createStageRunner(deps) {
|
|
|
4338
4547
|
streamEvent(writer.append(event));
|
|
4339
4548
|
if (event.type !== "state") deps.sendProgress({ jobId, kind: event.type, ts: event.ts, ...previewOf(event) });
|
|
4340
4549
|
};
|
|
4341
|
-
const mcpServers = agentConfig
|
|
4342
|
-
if (mcpServers && mcpServers.length > 0 && runtimeUsesMcpGateway(runtime)) {
|
|
4550
|
+
const mcpServers = agentConfig?.mcpServers;
|
|
4551
|
+
if (runtime && mcpServers && mcpServers.length > 0 && runtimeUsesMcpGateway(runtime)) {
|
|
4343
4552
|
gateway = await startMcpGateway({ servers: mcpServers, creds: job.brokeredCreds ?? {} });
|
|
4344
4553
|
}
|
|
4345
|
-
|
|
4554
|
+
let checkOutcomes = [];
|
|
4555
|
+
const runner = isCheck ? (deps.makeCheckRunner ?? createCheckRunner)(job.checks, (o) => {
|
|
4556
|
+
checkOutcomes = o;
|
|
4557
|
+
}) : deps.makeRunner(runtime);
|
|
4346
4558
|
active.set(jobId, runner);
|
|
4347
4559
|
const ctx = {
|
|
4348
|
-
config
|
|
4560
|
+
// A check runner reads only `workspace.worktreePath`; the empty config keeps the shared
|
|
4561
|
+
// RunnerContext shape without inventing a runtime the stage does not have.
|
|
4562
|
+
config: agentConfig ?? { runtime: "claude-code" },
|
|
4349
4563
|
workspace: ref,
|
|
4350
4564
|
sessionId: job.sessionId,
|
|
4351
4565
|
...job.issueContext !== void 0 ? { issueContext: job.issueContext } : {},
|
|
4352
4566
|
...job.guidance !== void 0 ? { guidance: job.guidance } : {},
|
|
4353
4567
|
...job.gateFeedback !== void 0 ? { gateFeedback: job.gateFeedback } : {},
|
|
4568
|
+
// Why this stage is running again, when it is. Without this passthrough the failing checks
|
|
4569
|
+
// reach the node and stop dead here, and the looped-back agent gets the stage's static prompt
|
|
4570
|
+
// with no hint of what it is meant to fix.
|
|
4571
|
+
...job.checkFailures !== void 0 ? { checkFailures: job.checkFailures } : {},
|
|
4354
4572
|
...job.attachedDocuments !== void 0 ? { attachedDocuments: job.attachedDocuments } : {},
|
|
4355
4573
|
...gateway ? { mcpProxyBaseUrl: gateway.baseUrl } : {},
|
|
4356
4574
|
// brokered inference env for a managed node (no operator login). The runtime adapter
|
|
@@ -4380,7 +4598,7 @@ function createStageRunner(deps) {
|
|
|
4380
4598
|
})
|
|
4381
4599
|
} : {}
|
|
4382
4600
|
};
|
|
4383
|
-
const interactive = agentConfig
|
|
4601
|
+
const interactive = agentConfig?.interaction === "interactive";
|
|
4384
4602
|
let result;
|
|
4385
4603
|
let timedOut = false;
|
|
4386
4604
|
const killMs = Math.max(0, Math.floor((job.timeout ?? 0) * 1e3));
|
|
@@ -4391,7 +4609,7 @@ function createStageRunner(deps) {
|
|
|
4391
4609
|
let stalled = false;
|
|
4392
4610
|
let stallTimer;
|
|
4393
4611
|
const stallSource = agentConfig.stallMs ?? Number(process.env.DAHRK_BATCH_STALL_MS ?? process.env.SKAKEL_BATCH_STALL_MS ?? 3e5);
|
|
4394
|
-
const stallMs = interactive ? 0 : Math.max(0, Math.floor(stallSource));
|
|
4612
|
+
const stallMs = interactive || isCheck ? 0 : Math.max(0, Math.floor(stallSource));
|
|
4395
4613
|
if (stallMs > 0) {
|
|
4396
4614
|
bumpStall = () => {
|
|
4397
4615
|
if (stallTimer) clearTimeout(stallTimer);
|
|
@@ -4418,7 +4636,7 @@ function createStageRunner(deps) {
|
|
|
4418
4636
|
if (status === "ok" && escapedUnblocked) {
|
|
4419
4637
|
status = "fail";
|
|
4420
4638
|
const msg = `stage reached outside the run's worktree and the ${runtime} runtime could not block it before it ran`;
|
|
4421
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime, kind: "fs-confine-escape", message: msg });
|
|
4639
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime: traceRuntime, kind: "fs-confine-escape", message: msg });
|
|
4422
4640
|
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: msg });
|
|
4423
4641
|
}
|
|
4424
4642
|
if (status === "ok" && job.hooks && job.hooks.length > 0) {
|
|
@@ -4427,7 +4645,7 @@ function createStageRunner(deps) {
|
|
|
4427
4645
|
execFileSync6("sh", ["-c", cmd], { cwd: ref.worktreePath, stdio: ["pipe", "pipe", "pipe"] });
|
|
4428
4646
|
} catch (e) {
|
|
4429
4647
|
status = "fail";
|
|
4430
|
-
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime, kind: "hook-failed", message: `hook "${cmd}" failed: ${e.message}` });
|
|
4648
|
+
writer.append({ seq: 0, ts: nowIso2(), type: "error", runtime: traceRuntime, kind: "hook-failed", message: `hook "${cmd}" failed: ${e.message}` });
|
|
4431
4649
|
break;
|
|
4432
4650
|
}
|
|
4433
4651
|
}
|
|
@@ -4444,7 +4662,16 @@ function createStageRunner(deps) {
|
|
|
4444
4662
|
}
|
|
4445
4663
|
if (denied) summary += "\n\n(note: one or more tool actions were blocked by a deny-only policy guard.)";
|
|
4446
4664
|
const failureClass = timedOut || stalled ? "harness" : result.failureClass;
|
|
4447
|
-
|
|
4665
|
+
if (isCheck && ref) writeCheckNote(ref, stageId, attempt, checkOutcomes);
|
|
4666
|
+
return finish(
|
|
4667
|
+
status,
|
|
4668
|
+
summary,
|
|
4669
|
+
result.sessionId ?? job.sessionId,
|
|
4670
|
+
result.costUsd,
|
|
4671
|
+
result.artifact,
|
|
4672
|
+
failureClass,
|
|
4673
|
+
result.verifications
|
|
4674
|
+
);
|
|
4448
4675
|
} finally {
|
|
4449
4676
|
inFlight.set(runId, Math.max(0, (inFlight.get(runId) ?? 1) - 1));
|
|
4450
4677
|
}
|
|
@@ -4531,6 +4758,7 @@ function createStageRunner(deps) {
|
|
|
4531
4758
|
var ENROLMENT_REJECTED_EXIT_CODE = 78;
|
|
4532
4759
|
var RECONNECT_BASE_MS = 500;
|
|
4533
4760
|
var RECONNECT_MAX_MS = 3e4;
|
|
4761
|
+
var NODE_CAPABILITIES = ["check"];
|
|
4534
4762
|
var PARK_POLL_MS = 6e4;
|
|
4535
4763
|
function classifyError(e) {
|
|
4536
4764
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -4714,6 +4942,12 @@ async function startEdgeNode(opts) {
|
|
|
4714
4942
|
os: osPlatform(),
|
|
4715
4943
|
arch: osArch(),
|
|
4716
4944
|
clientVersion: opts.clientVersion ?? "0.0.0",
|
|
4945
|
+
// Non-runtime job kinds this build can execute. The hub REFUSES to place a check job on a node
|
|
4946
|
+
// that does not advertise `check`, and that refusal is a safety gate rather than an optimisation:
|
|
4947
|
+
// `makeRunner()` defaults to Claude rather than erroring on an unknown job shape, so an old client
|
|
4948
|
+
// handed a check job would boot an unbounded write-access agent with the fallback instruction
|
|
4949
|
+
// "Begin the stage.", report ok, and let the run reach deliver green having run no checks.
|
|
4950
|
+
capabilities: NODE_CAPABILITIES,
|
|
4717
4951
|
// Advertise the resolved worktree base so the hub records each run's real worktree location in
|
|
4718
4952
|
// the projection instead of an advisory placeholder. Single-sourced from the git service so it
|
|
4719
4953
|
// always matches where worktrees actually land.
|
|
@@ -5046,7 +5280,7 @@ var PROBES = [
|
|
|
5046
5280
|
{ runtime: "claude-code", cmd: "claude" },
|
|
5047
5281
|
{ runtime: "pi", cmd: "pi" }
|
|
5048
5282
|
];
|
|
5049
|
-
var
|
|
5283
|
+
var DEFAULT_TIMEOUT_MS3 = 5e3;
|
|
5050
5284
|
var DEFAULT_ATTEMPTS = 2;
|
|
5051
5285
|
function probeOnce(cmd, timeoutMs) {
|
|
5052
5286
|
return new Promise((resolve3) => {
|
|
@@ -5068,14 +5302,14 @@ async function probe(cmd, timeoutMs, attempts) {
|
|
|
5068
5302
|
}
|
|
5069
5303
|
return void 0;
|
|
5070
5304
|
}
|
|
5071
|
-
async function probeRuntimeStatuses(timeoutMs =
|
|
5305
|
+
async function probeRuntimeStatuses(timeoutMs = DEFAULT_TIMEOUT_MS3, attempts = DEFAULT_ATTEMPTS) {
|
|
5072
5306
|
const versions = await Promise.all(PROBES.map((p) => probe(p.cmd, timeoutMs, attempts)));
|
|
5073
5307
|
return PROBES.map((p, i) => {
|
|
5074
5308
|
const version = versions[i];
|
|
5075
5309
|
return version === void 0 ? { runtime: p.runtime, cmd: p.cmd, installed: false } : { runtime: p.runtime, cmd: p.cmd, installed: true, version };
|
|
5076
5310
|
});
|
|
5077
5311
|
}
|
|
5078
|
-
async function detectRuntimes(timeoutMs =
|
|
5312
|
+
async function detectRuntimes(timeoutMs = DEFAULT_TIMEOUT_MS3, attempts = DEFAULT_ATTEMPTS) {
|
|
5079
5313
|
const statuses = await probeRuntimeStatuses(timeoutMs, attempts);
|
|
5080
5314
|
return statuses.filter((s) => s.installed).map((s) => s.runtime);
|
|
5081
5315
|
}
|
|
@@ -6132,7 +6366,7 @@ var defaultLockDeps = (file) => ({
|
|
|
6132
6366
|
});
|
|
6133
6367
|
|
|
6134
6368
|
// src/logs.ts
|
|
6135
|
-
import { spawn } from "child_process";
|
|
6369
|
+
import { spawn as spawn2 } from "child_process";
|
|
6136
6370
|
import { copyFileSync, existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync4, truncateSync } from "fs";
|
|
6137
6371
|
var MAX_LOG_BYTES = 10 * 1024 * 1024;
|
|
6138
6372
|
function logsCommand(inputs) {
|
|
@@ -6227,7 +6461,7 @@ var defaultLogsDeps = (files, jsonlFile) => ({
|
|
|
6227
6461
|
readFile: (path) => readFileSync9(path, "utf8"),
|
|
6228
6462
|
run: (argv) => new Promise((resolve3) => {
|
|
6229
6463
|
const [cmd, ...args] = argv;
|
|
6230
|
-
const child =
|
|
6464
|
+
const child = spawn2(cmd, args, { stdio: "inherit" });
|
|
6231
6465
|
child.on("error", () => resolve3(1));
|
|
6232
6466
|
child.on("close", (code) => resolve3(code ?? 0));
|
|
6233
6467
|
}),
|
|
@@ -7509,7 +7743,7 @@ async function runStatus(inputs, deps) {
|
|
|
7509
7743
|
}
|
|
7510
7744
|
|
|
7511
7745
|
// src/main.ts
|
|
7512
|
-
var CLIENT_VERSION = "0.1.
|
|
7746
|
+
var CLIENT_VERSION = "0.1.27";
|
|
7513
7747
|
var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
|
|
7514
7748
|
var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
7515
7749
|
var RUNTIMES2 = ["claude-code", "codex", "pi"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dahrk-node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"exports": "./dist/main.js",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@anthropic-ai/claude-agent-sdk": "0.3.183",
|
|
37
|
-
"@dahrk/contracts": "^0.
|
|
37
|
+
"@dahrk/contracts": "^0.8.2",
|
|
38
38
|
"pino": "^10.3.1",
|
|
39
39
|
"ws": "^8.18.0",
|
|
40
40
|
"zod": "^3.23.8"
|