dahrk-node 0.1.20 → 0.1.21
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/chunk-FYS2JH42.js +31 -0
- package/dist/chunk-G3AU5H5X.js +1568 -0
- package/dist/client-MZ5GEQAD.js +8745 -0
- package/dist/main.js +734 -424
- package/dist/streamableHttp-S27HYW5J.js +1481 -0
- package/package.json +2 -3
package/dist/main.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-FYS2JH42.js";
|
|
2
3
|
|
|
3
4
|
// src/main.ts
|
|
4
5
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
5
6
|
import { existsSync as existsSync14, readFileSync as readFileSync11, realpathSync as realpathSync5 } from "fs";
|
|
6
7
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
7
8
|
import { homedir as homedir7, platform as osPlatform4 } from "os";
|
|
8
|
-
import { basename as basename2, join as
|
|
9
|
+
import { basename as basename2, join as join19 } from "path";
|
|
9
10
|
import { pathToFileURL } from "url";
|
|
10
11
|
|
|
11
12
|
// ../../packages/edge/src/ws-client.ts
|
|
@@ -232,7 +233,7 @@ function documentsBlock(ctx) {
|
|
|
232
233
|
let budget = MAX_INLINE_DOCS_TOTAL_CHARS;
|
|
233
234
|
const parts = [];
|
|
234
235
|
for (const doc of docs) {
|
|
235
|
-
const path = `.
|
|
236
|
+
const path = `.dahrk/scratch/docs/${attachedDocBasename(doc)}.md`;
|
|
236
237
|
const cap = Math.max(0, Math.min(MAX_INLINE_DOC_CHARS, budget));
|
|
237
238
|
const body = doc.content.trim();
|
|
238
239
|
const truncated = body.length > cap;
|
|
@@ -497,7 +498,7 @@ function createAskUserQuestionTool(deps) {
|
|
|
497
498
|
// ../../packages/executor-worktree/src/claude-adapter.ts
|
|
498
499
|
var COALESCE_MS = Number(process.env.DAHRK_COALESCE_MS ?? process.env.SKAKEL_COALESCE_MS ?? 40);
|
|
499
500
|
var MAX_TURNS = Number(process.env.DAHRK_MAX_TURNS ?? process.env.SKAKEL_MAX_TURNS ?? 64);
|
|
500
|
-
var HANDED_BACK_ARTIFACT_PATH = ".
|
|
501
|
+
var HANDED_BACK_ARTIFACT_PATH = ".dahrk/scratch/output/document.md";
|
|
501
502
|
var CLAUDE_CODE_SYSTEM_PROMPT = { type: "preset", preset: "claude_code" };
|
|
502
503
|
var userMsg = (text) => ({
|
|
503
504
|
type: "user",
|
|
@@ -828,237 +829,11 @@ function createClaudeRunner() {
|
|
|
828
829
|
};
|
|
829
830
|
}
|
|
830
831
|
|
|
831
|
-
// ../../packages/executor-worktree/src/
|
|
832
|
-
import {
|
|
833
|
-
|
|
834
|
-
// ../../packages/executor-worktree/src/codex-mappers.ts
|
|
835
|
-
function mapItem(item) {
|
|
836
|
-
switch (item.type) {
|
|
837
|
-
case "reasoning":
|
|
838
|
-
return [{ type: "thought", subtype: "reasoning_text", text: item.text }];
|
|
839
|
-
case "agent_message":
|
|
840
|
-
return [{ type: "response", text: item.text }];
|
|
841
|
-
case "command_execution":
|
|
842
|
-
return [
|
|
843
|
-
{ type: "action", tool: "command", toolUseId: item.id, input: { command: item.command } },
|
|
844
|
-
{ type: "observation", toolUseId: item.id, output: item.aggregated_output, isError: item.status === "failed" }
|
|
845
|
-
];
|
|
846
|
-
case "mcp_tool_call":
|
|
847
|
-
return [
|
|
848
|
-
{ type: "action", tool: `${item.server}/${item.tool}`, toolUseId: item.id, input: item.arguments },
|
|
849
|
-
{ type: "observation", toolUseId: item.id, output: item.result?.content ?? item.error, isError: Boolean(item.error) }
|
|
850
|
-
];
|
|
851
|
-
case "web_search":
|
|
852
|
-
return [{ type: "action", tool: "web_search", toolUseId: item.id, input: { query: item.query } }];
|
|
853
|
-
case "file_change":
|
|
854
|
-
return [{ type: "action", tool: "apply_patch", toolUseId: item.id, input: item.changes }];
|
|
855
|
-
case "todo_list":
|
|
856
|
-
return [{ type: "thought", text: JSON.stringify(item.items) }];
|
|
857
|
-
case "error":
|
|
858
|
-
return [{ type: "error", kind: "item_error", message: item.message }];
|
|
859
|
-
default:
|
|
860
|
-
return [];
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
function mapCodexEvent(ev) {
|
|
864
|
-
switch (ev.type) {
|
|
865
|
-
case "item.completed":
|
|
866
|
-
return { events: mapItem(ev.item), recognised: true };
|
|
867
|
-
case "turn.completed":
|
|
868
|
-
return {
|
|
869
|
-
events: [
|
|
870
|
-
{ type: "state", event: "stage-exit", status: "ok", usage: mapUsage2(ev.usage) }
|
|
871
|
-
],
|
|
872
|
-
recognised: true
|
|
873
|
-
};
|
|
874
|
-
case "turn.failed":
|
|
875
|
-
return {
|
|
876
|
-
events: [
|
|
877
|
-
{ type: "error", kind: "turn_failed", message: JSON.stringify(ev.error ?? {}) },
|
|
878
|
-
{ type: "state", event: "stage-exit", status: "fail" }
|
|
879
|
-
],
|
|
880
|
-
recognised: true
|
|
881
|
-
};
|
|
882
|
-
case "error":
|
|
883
|
-
return { events: [{ type: "error", kind: "thread_error", message: ev.message }], recognised: true };
|
|
884
|
-
// Lifecycle / interim updates: recognised, captured in raw sidecar, not normalised.
|
|
885
|
-
case "thread.started":
|
|
886
|
-
case "turn.started":
|
|
887
|
-
case "item.started":
|
|
888
|
-
case "item.updated":
|
|
889
|
-
return { events: [], recognised: true };
|
|
890
|
-
default:
|
|
891
|
-
return { events: [], recognised: false };
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
function mapUsage2(u) {
|
|
895
|
-
return {
|
|
896
|
-
input: u?.input_tokens ?? 0,
|
|
897
|
-
output: u?.output_tokens ?? 0,
|
|
898
|
-
cacheRead: u?.cached_input_tokens ?? 0,
|
|
899
|
-
cacheCreate: 0
|
|
900
|
-
};
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// ../../packages/executor-worktree/src/codex-adapter.ts
|
|
904
|
-
var COALESCE_MS2 = Number(process.env.DAHRK_COALESCE_MS ?? process.env.SKAKEL_COALESCE_MS ?? 40);
|
|
905
|
-
var CODEX_COST_UNAVAILABLE_NOTE = "codex-adapter: cost reporting unavailable for the Codex runtime (the SDK reports tokens, not price); costUsd left unset, not $0\n";
|
|
906
|
-
function warnCostUnavailable(write = (s) => void process.stderr.write(s)) {
|
|
907
|
-
write(CODEX_COST_UNAVAILABLE_NOTE);
|
|
908
|
-
}
|
|
909
|
-
function runtimeEnvOptions2(ctx) {
|
|
910
|
-
if (!ctx.runtimeEnv) return {};
|
|
911
|
-
const env = {};
|
|
912
|
-
for (const [k, v] of Object.entries(process.env)) if (v !== void 0) env[k] = v;
|
|
913
|
-
return { env: { ...env, ...ctx.runtimeEnv } };
|
|
914
|
-
}
|
|
915
|
-
function createCodexRunner() {
|
|
916
|
-
const abortController = new AbortController();
|
|
917
|
-
const signal = abortController.signal;
|
|
918
|
-
let cancelled = false;
|
|
919
|
-
let thread;
|
|
920
|
-
let sessionId;
|
|
921
|
-
const threadOptions = (ctx) => ({
|
|
922
|
-
workingDirectory: ctx.workspace.worktreePath,
|
|
923
|
-
sandboxMode: "workspace-write",
|
|
924
|
-
skipGitRepoCheck: true,
|
|
925
|
-
...ctx.config.model ? { model: ctx.config.model } : {}
|
|
926
|
-
});
|
|
927
|
-
const openThread = (ctx) => {
|
|
928
|
-
const codex = new Codex(runtimeEnvOptions2(ctx));
|
|
929
|
-
const t = ctx.sessionId ? codex.resumeThread(ctx.sessionId, threadOptions(ctx)) : codex.startThread(threadOptions(ctx));
|
|
930
|
-
thread = t;
|
|
931
|
-
return t;
|
|
932
|
-
};
|
|
933
|
-
const pumpTurn = async (events, emit, ctx, suppressStageExit) => {
|
|
934
|
-
let failed = false;
|
|
935
|
-
for await (const ev of events) {
|
|
936
|
-
const rawRef = ctx.writeRaw?.(ev);
|
|
937
|
-
const { events: mapped } = mapCodexEvent(ev);
|
|
938
|
-
for (const e of mapped) {
|
|
939
|
-
if (suppressStageExit && e.type === "state") continue;
|
|
940
|
-
emit(e, rawRef);
|
|
941
|
-
}
|
|
942
|
-
if (ev.type === "thread.started") sessionId = ev.thread_id;
|
|
943
|
-
if (ev.type === "turn.failed") failed = true;
|
|
944
|
-
}
|
|
945
|
-
return failed;
|
|
946
|
-
};
|
|
947
|
-
const captureThreadId = (t) => {
|
|
948
|
-
if (t.id) sessionId = t.id;
|
|
949
|
-
};
|
|
950
|
-
return {
|
|
951
|
-
runtime: "codex",
|
|
952
|
-
async runBatch(ctx, onTrace) {
|
|
953
|
-
if (ctx.config.mcpServers && ctx.config.mcpServers.length > 0) {
|
|
954
|
-
process.stderr.write("codex-adapter: MCP servers not supported on Codex; ignoring\n");
|
|
955
|
-
}
|
|
956
|
-
const emit = makeEmit("codex", onTrace);
|
|
957
|
-
const t = openThread(ctx);
|
|
958
|
-
let status = "ok";
|
|
959
|
-
try {
|
|
960
|
-
const { events } = await t.runStreamed(resolveStagePrompt(ctx), { signal });
|
|
961
|
-
if (await pumpTurn(events, emit, ctx, false)) status = "fail";
|
|
962
|
-
} catch (e) {
|
|
963
|
-
if (!cancelled) emit({ type: "error", kind: "runtime_error", message: e.message });
|
|
964
|
-
status = "fail";
|
|
965
|
-
}
|
|
966
|
-
captureThreadId(t);
|
|
967
|
-
if (cancelled) status = "fail";
|
|
968
|
-
warnCostUnavailable();
|
|
969
|
-
return { status, ...sessionId ? { sessionId } : {} };
|
|
970
|
-
},
|
|
971
|
-
async runInteractive(ctx, turns, onTrace) {
|
|
972
|
-
const emit = makeEmit("codex", onTrace);
|
|
973
|
-
const t = openThread(ctx);
|
|
974
|
-
const exit = ctx.config.exit ?? "either";
|
|
975
|
-
if (exit === "tool" || exit === "either") {
|
|
976
|
-
process.stderr.write("codex-adapter: interactive tool-exit not supported in M4; using gate exit\n");
|
|
977
|
-
}
|
|
978
|
-
const humanIter = turns[Symbol.asyncIterator]();
|
|
979
|
-
const { firstReplyMs, idleMs } = interactiveIdleWindows(ctx);
|
|
980
|
-
let awaitingFirstReply = true;
|
|
981
|
-
let exited = "gate";
|
|
982
|
-
let pending = humanIter.next();
|
|
983
|
-
try {
|
|
984
|
-
const seed = await t.runStreamed(interactiveSeedText(ctx, false), { signal });
|
|
985
|
-
await pumpTurn(seed.events, emit, ctx, true);
|
|
986
|
-
for (; ; ) {
|
|
987
|
-
const race = await raceNextTurn(pending, awaitingFirstReply ? firstReplyMs : idleMs, signal);
|
|
988
|
-
awaitingFirstReply = false;
|
|
989
|
-
if (race.kind === "cancelled") {
|
|
990
|
-
exited = "cancelled";
|
|
991
|
-
break;
|
|
992
|
-
}
|
|
993
|
-
if (race.kind === "idle-timeout") {
|
|
994
|
-
exited = "timeout";
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
if (race.kind === "turns-exhausted") {
|
|
998
|
-
exited = "gate";
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
const texts = [race.value.text];
|
|
1002
|
-
pending = humanIter.next();
|
|
1003
|
-
for (; ; ) {
|
|
1004
|
-
const more = await raceNextTurn(pending, COALESCE_MS2, signal);
|
|
1005
|
-
if (more.kind === "turn") {
|
|
1006
|
-
texts.push(more.value.text);
|
|
1007
|
-
pending = humanIter.next();
|
|
1008
|
-
continue;
|
|
1009
|
-
}
|
|
1010
|
-
if (more.kind === "cancelled") exited = "cancelled";
|
|
1011
|
-
break;
|
|
1012
|
-
}
|
|
1013
|
-
if (exited === "cancelled") break;
|
|
1014
|
-
const { events } = await t.runStreamed(texts.join("\n"), { signal });
|
|
1015
|
-
await pumpTurn(events, emit, ctx, true);
|
|
1016
|
-
}
|
|
1017
|
-
} catch (e) {
|
|
1018
|
-
if (!cancelled) emit({ type: "error", kind: "runtime_error", message: e.message });
|
|
1019
|
-
exited = cancelled ? "cancelled" : "gate";
|
|
1020
|
-
}
|
|
1021
|
-
let status = "ok";
|
|
1022
|
-
let summary = "";
|
|
1023
|
-
if (exited === "gate") {
|
|
1024
|
-
try {
|
|
1025
|
-
const turn = await t.run(SUMMARISE_PROMPT, { signal });
|
|
1026
|
-
summary = (turn.finalResponse ?? "").trim() || "(no summary produced)";
|
|
1027
|
-
} catch {
|
|
1028
|
-
summary = "(no summary produced)";
|
|
1029
|
-
}
|
|
1030
|
-
} else if (exited === "timeout") {
|
|
1031
|
-
status = "timeout";
|
|
1032
|
-
summary = "(stage timed out awaiting input)";
|
|
1033
|
-
await this.cancel();
|
|
1034
|
-
} else {
|
|
1035
|
-
status = "fail";
|
|
1036
|
-
summary = "(stage cancelled)";
|
|
1037
|
-
}
|
|
1038
|
-
captureThreadId(t);
|
|
1039
|
-
warnCostUnavailable();
|
|
1040
|
-
return { status, summary, ...sessionId ? { sessionId } : {} };
|
|
1041
|
-
},
|
|
1042
|
-
async summarise(ctx) {
|
|
1043
|
-
if (!thread) return "(no summary: thread not established)";
|
|
1044
|
-
try {
|
|
1045
|
-
const turn = await thread.run(SUMMARISE_PROMPT, { signal });
|
|
1046
|
-
captureThreadId(thread);
|
|
1047
|
-
return (turn.finalResponse ?? "").trim() || "(no summary produced)";
|
|
1048
|
-
} catch (e) {
|
|
1049
|
-
return `(summary unavailable: ${e.message})`;
|
|
1050
|
-
}
|
|
1051
|
-
},
|
|
1052
|
-
async cancel() {
|
|
1053
|
-
if (cancelled) return;
|
|
1054
|
-
cancelled = true;
|
|
1055
|
-
abortController.abort();
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
832
|
+
// ../../packages/executor-worktree/src/pi-adapter.ts
|
|
833
|
+
import { join as join4 } from "path";
|
|
1059
834
|
|
|
1060
835
|
// ../../packages/executor-worktree/src/pi-mappers.ts
|
|
1061
|
-
function
|
|
836
|
+
function mapUsage2(u) {
|
|
1062
837
|
return {
|
|
1063
838
|
input: u?.input ?? 0,
|
|
1064
839
|
output: u?.output ?? 0,
|
|
@@ -1099,7 +874,7 @@ function mapPiEvent(ev) {
|
|
|
1099
874
|
const kind = ev.type === "agent_end" ? "agent_error" : "turn_error";
|
|
1100
875
|
events.push({ type: "error", kind, message: m?.errorMessage ?? m?.stopReason ?? "failed" });
|
|
1101
876
|
}
|
|
1102
|
-
events.push({ type: "state", event: "stage-exit", status, usage:
|
|
877
|
+
events.push({ type: "state", event: "stage-exit", status, usage: mapUsage2(m?.usage) });
|
|
1103
878
|
return { events, recognised: true };
|
|
1104
879
|
}
|
|
1105
880
|
// Streamed deltas: owned by the buffered state machine, no discrete event here.
|
|
@@ -1175,9 +950,117 @@ function consumePiEvent(ev, state, suppressStageExit) {
|
|
|
1175
950
|
return { events, isResult: false };
|
|
1176
951
|
}
|
|
1177
952
|
|
|
953
|
+
// ../../packages/executor-worktree/src/pi-auth.ts
|
|
954
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
955
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
956
|
+
import { join as join3 } from "path";
|
|
957
|
+
function readAuthHint(ctx) {
|
|
958
|
+
return ctx.runtimeAuth;
|
|
959
|
+
}
|
|
960
|
+
function applyApiKeyAuth(hint2, runtimeEnv, authStorage) {
|
|
961
|
+
const env = runtimeEnv ?? {};
|
|
962
|
+
for (const p of hint2?.providers ?? []) {
|
|
963
|
+
if (p.kind !== "api_key") continue;
|
|
964
|
+
const value = env[p.envVar];
|
|
965
|
+
if (value) authStorage.setRuntimeApiKey(p.provider, value);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function buildAuthJson(hint2) {
|
|
969
|
+
const entries = [];
|
|
970
|
+
for (const p of hint2?.providers ?? []) {
|
|
971
|
+
if (p.kind !== "oauth") continue;
|
|
972
|
+
entries.push([p.provider, { type: "oauth", ...p.extra, access: p.access, refresh: p.refresh, expires: p.expires }]);
|
|
973
|
+
}
|
|
974
|
+
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
975
|
+
}
|
|
976
|
+
function buildCustomProviders(hint2) {
|
|
977
|
+
const providers = {};
|
|
978
|
+
for (const p of hint2?.providers ?? []) {
|
|
979
|
+
if (p.kind !== "api_key" || !p.baseUrl) continue;
|
|
980
|
+
providers[p.provider] = {
|
|
981
|
+
baseUrl: p.baseUrl,
|
|
982
|
+
...p.headers ? { headers: p.headers } : {},
|
|
983
|
+
...p.models ? { models: p.models } : {}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
return Object.keys(providers).length ? { providers } : void 0;
|
|
987
|
+
}
|
|
988
|
+
function createStageConfigDir() {
|
|
989
|
+
return mkdtempSync(join3(tmpdir2(), "dahrk-pi-"));
|
|
990
|
+
}
|
|
991
|
+
function cleanupStageConfigDir(dir) {
|
|
992
|
+
rmSync(dir, { recursive: true, force: true });
|
|
993
|
+
}
|
|
994
|
+
function writeStageAuthFile(dir, hint2) {
|
|
995
|
+
const authJson = buildAuthJson(hint2);
|
|
996
|
+
if (!authJson) return void 0;
|
|
997
|
+
const path = join3(dir, "auth.json");
|
|
998
|
+
writeFileSync(path, JSON.stringify(authJson, null, 2));
|
|
999
|
+
return path;
|
|
1000
|
+
}
|
|
1001
|
+
function writeStageCustomProviders(dir, hint2) {
|
|
1002
|
+
const models = buildCustomProviders(hint2);
|
|
1003
|
+
if (!models) return void 0;
|
|
1004
|
+
const path = join3(dir, "models.json");
|
|
1005
|
+
writeFileSync(path, JSON.stringify(models, null, 2));
|
|
1006
|
+
return path;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1178
1009
|
// ../../packages/executor-worktree/src/pi-adapter.ts
|
|
1179
|
-
var
|
|
1010
|
+
var COALESCE_MS2 = Number(process.env.DAHRK_COALESCE_MS ?? process.env.SKAKEL_COALESCE_MS ?? 40);
|
|
1180
1011
|
var PI_STAGE_COMPLETE_TOOL = "dahrk_stage_complete";
|
|
1012
|
+
var PI_ASK_USER_QUESTION_TOOL = "ask_user_question";
|
|
1013
|
+
function piToolCallDecision(ctx, toolName, input) {
|
|
1014
|
+
const verdict2 = ctx.authorizeToolUse?.(toolName, input);
|
|
1015
|
+
if (verdict2?.verdict === "deny") {
|
|
1016
|
+
return { block: true, reason: verdict2.reason ?? `tool "${toolName}" denied by policy ${verdict2.policy}` };
|
|
1017
|
+
}
|
|
1018
|
+
return void 0;
|
|
1019
|
+
}
|
|
1020
|
+
function registerToolCallGate(s, ctx) {
|
|
1021
|
+
const policyCtx = ctx;
|
|
1022
|
+
s.setToolCallGate?.((toolName, input) => piToolCallDecision(policyCtx, toolName, input));
|
|
1023
|
+
}
|
|
1024
|
+
function buildBrokeredPiMcpServers(ctx) {
|
|
1025
|
+
const servers = ctx.config.mcpServers;
|
|
1026
|
+
if (!servers || servers.length === 0 || !ctx.mcpProxyBaseUrl) return void 0;
|
|
1027
|
+
const entries = {};
|
|
1028
|
+
for (const s of servers) entries[s.id] = { type: s.type, url: `${ctx.mcpProxyBaseUrl}/${s.id}` };
|
|
1029
|
+
return entries;
|
|
1030
|
+
}
|
|
1031
|
+
function createBrokeredMcpExtension(servers) {
|
|
1032
|
+
return {
|
|
1033
|
+
name: "dahrk-brokered-mcp",
|
|
1034
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1035
|
+
factory: async (pi) => {
|
|
1036
|
+
const { Client } = await import("./client-MZ5GEQAD.js");
|
|
1037
|
+
const { StreamableHTTPClientTransport } = await import("./streamableHttp-S27HYW5J.js");
|
|
1038
|
+
for (const [id, server] of Object.entries(servers)) {
|
|
1039
|
+
try {
|
|
1040
|
+
const client = new Client({ name: `dahrk-${id}`, version: "0.1.0" });
|
|
1041
|
+
await client.connect(new StreamableHTTPClientTransport(new URL(server.url)));
|
|
1042
|
+
const { tools } = await client.listTools();
|
|
1043
|
+
for (const tool3 of tools) {
|
|
1044
|
+
pi.registerTool({
|
|
1045
|
+
name: tool3.name,
|
|
1046
|
+
label: tool3.name,
|
|
1047
|
+
description: tool3.description ?? "",
|
|
1048
|
+
// The MCP inputSchema is a JSON-schema object; Pi validates against it at runtime (its
|
|
1049
|
+
// custom tools use the same plain-object shape - see the injected tools above).
|
|
1050
|
+
parameters: tool3.inputSchema,
|
|
1051
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1052
|
+
execute: async (_toolCallId, params) => {
|
|
1053
|
+
const result = await client.callTool({ name: tool3.name, arguments: params ?? {} });
|
|
1054
|
+
return { content: result.content, details: {} };
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1181
1064
|
function createPiRunner(deps = {}) {
|
|
1182
1065
|
const createSession = deps.createSession ?? defaultCreatePiSession;
|
|
1183
1066
|
const abortController = new AbortController();
|
|
@@ -1192,6 +1075,15 @@ function createPiRunner(deps = {}) {
|
|
|
1192
1075
|
const captureSessionId = (s) => {
|
|
1193
1076
|
if (s.sessionId) sessionId = s.sessionId;
|
|
1194
1077
|
};
|
|
1078
|
+
let sessionDisposed = false;
|
|
1079
|
+
const disposeSession = () => {
|
|
1080
|
+
if (sessionDisposed || !session) return;
|
|
1081
|
+
sessionDisposed = true;
|
|
1082
|
+
try {
|
|
1083
|
+
session.dispose();
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1195
1087
|
const readSessionCost = (s) => {
|
|
1196
1088
|
const cost = s.getSessionStats?.()?.cost;
|
|
1197
1089
|
return typeof cost === "number" ? cost : void 0;
|
|
@@ -1201,6 +1093,7 @@ function createPiRunner(deps = {}) {
|
|
|
1201
1093
|
async runBatch(ctx, onTrace) {
|
|
1202
1094
|
const emit = makeEmit("pi", onTrace);
|
|
1203
1095
|
const s = await openSession(ctx);
|
|
1096
|
+
registerToolCallGate(s, ctx);
|
|
1204
1097
|
const state = newPiBufferState();
|
|
1205
1098
|
let status = "ok";
|
|
1206
1099
|
const unsub = s.subscribe((ev) => {
|
|
@@ -1220,11 +1113,13 @@ function createPiRunner(deps = {}) {
|
|
|
1220
1113
|
captureSessionId(s);
|
|
1221
1114
|
if (cancelled) status = "fail";
|
|
1222
1115
|
const costUsd = readSessionCost(s);
|
|
1116
|
+
if (status !== "ok") disposeSession();
|
|
1223
1117
|
return { status, ...sessionId ? { sessionId } : {}, ...costUsd !== void 0 ? { costUsd } : {} };
|
|
1224
1118
|
},
|
|
1225
1119
|
async runInteractive(ctx, turns, onTrace) {
|
|
1226
1120
|
const emit = makeEmit("pi", onTrace);
|
|
1227
1121
|
const s = await openSession(ctx);
|
|
1122
|
+
registerToolCallGate(s, ctx);
|
|
1228
1123
|
const state = newPiBufferState();
|
|
1229
1124
|
const exit = ctx.config.exit ?? "either";
|
|
1230
1125
|
const wantsTool = exit === "tool" || exit === "either";
|
|
@@ -1246,9 +1141,28 @@ function createPiRunner(deps = {}) {
|
|
|
1246
1141
|
for (const e of r.events) emit(e, rawRef);
|
|
1247
1142
|
if (r.responseText) lastResponseText = r.responseText;
|
|
1248
1143
|
});
|
|
1249
|
-
const humanIter = turns[Symbol.asyncIterator]();
|
|
1250
1144
|
const { firstReplyMs, idleMs } = interactiveIdleWindows(ctx);
|
|
1145
|
+
const router = createElicitTurnRouter(turns, { signal, firstReplyMs, idleMs });
|
|
1146
|
+
const humanIter = router.conversation[Symbol.asyncIterator]();
|
|
1251
1147
|
let awaitingFirstReply = true;
|
|
1148
|
+
const elicitCtx = ctx;
|
|
1149
|
+
const ask = async (question) => {
|
|
1150
|
+
const outcome = await router.ask(awaitingFirstReply, () => {
|
|
1151
|
+
emit({ type: "elicitation", prompt: question.prompt, signal: "select", options: question.options });
|
|
1152
|
+
elicitCtx.emitElicit?.(question);
|
|
1153
|
+
});
|
|
1154
|
+
switch (outcome.kind) {
|
|
1155
|
+
case "reply":
|
|
1156
|
+
return `The user selected: ${outcome.text}`;
|
|
1157
|
+
case "busy":
|
|
1158
|
+
return "Only one question can be asked at a time; wait for the current one to be answered, then ask again.";
|
|
1159
|
+
case "noreply":
|
|
1160
|
+
return "No response from the user; proceed with your best judgement.";
|
|
1161
|
+
case "cancel":
|
|
1162
|
+
return "The question was cancelled.";
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
s.setAskUserQuestionHandler?.((questions) => askQuestionsSequentially(questions, ask));
|
|
1252
1166
|
let exited = "gate";
|
|
1253
1167
|
let pending = humanIter.next();
|
|
1254
1168
|
try {
|
|
@@ -1273,7 +1187,7 @@ function createPiRunner(deps = {}) {
|
|
|
1273
1187
|
const texts = [race.value.text];
|
|
1274
1188
|
pending = humanIter.next();
|
|
1275
1189
|
for (; ; ) {
|
|
1276
|
-
const more = await raceNextTurn(pending,
|
|
1190
|
+
const more = await raceNextTurn(pending, COALESCE_MS2, signal);
|
|
1277
1191
|
if (more.kind === "turn") {
|
|
1278
1192
|
texts.push(more.value.text);
|
|
1279
1193
|
pending = humanIter.next();
|
|
@@ -1316,6 +1230,7 @@ function createPiRunner(deps = {}) {
|
|
|
1316
1230
|
unsub();
|
|
1317
1231
|
captureSessionId(s);
|
|
1318
1232
|
const costUsd = readSessionCost(s);
|
|
1233
|
+
disposeSession();
|
|
1319
1234
|
return {
|
|
1320
1235
|
status,
|
|
1321
1236
|
summary,
|
|
@@ -1341,6 +1256,7 @@ function createPiRunner(deps = {}) {
|
|
|
1341
1256
|
return `(summary unavailable: ${e.message})`;
|
|
1342
1257
|
} finally {
|
|
1343
1258
|
unsub();
|
|
1259
|
+
disposeSession();
|
|
1344
1260
|
}
|
|
1345
1261
|
},
|
|
1346
1262
|
async cancel() {
|
|
@@ -1351,23 +1267,31 @@ function createPiRunner(deps = {}) {
|
|
|
1351
1267
|
await session?.abort();
|
|
1352
1268
|
} catch {
|
|
1353
1269
|
}
|
|
1354
|
-
|
|
1355
|
-
session?.dispose();
|
|
1356
|
-
} catch {
|
|
1357
|
-
}
|
|
1270
|
+
disposeSession();
|
|
1358
1271
|
}
|
|
1359
1272
|
};
|
|
1360
1273
|
}
|
|
1361
1274
|
async function defaultCreatePiSession(ctx) {
|
|
1362
1275
|
const spec = "@earendil-works/pi-coding-agent";
|
|
1363
1276
|
const mod = await import(spec);
|
|
1364
|
-
const {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1277
|
+
const {
|
|
1278
|
+
AuthStorage,
|
|
1279
|
+
DefaultResourceLoader,
|
|
1280
|
+
ModelRegistry,
|
|
1281
|
+
SessionManager,
|
|
1282
|
+
SettingsManager,
|
|
1283
|
+
createAgentSession,
|
|
1284
|
+
defineTool,
|
|
1285
|
+
getAgentDir,
|
|
1286
|
+
resolveCliModel
|
|
1287
|
+
} = mod;
|
|
1288
|
+
const hint2 = readAuthHint(ctx);
|
|
1289
|
+
const configDir = createStageConfigDir();
|
|
1290
|
+
writeStageAuthFile(configDir, hint2);
|
|
1291
|
+
writeStageCustomProviders(configDir, hint2);
|
|
1292
|
+
const authStorage = AuthStorage.create(join4(configDir, "auth.json"));
|
|
1293
|
+
applyApiKeyAuth(hint2, ctx.runtimeEnv, authStorage);
|
|
1294
|
+
const modelRegistry = ModelRegistry.create(authStorage, join4(configDir, "models.json"));
|
|
1371
1295
|
let model;
|
|
1372
1296
|
if (ctx.config.model) {
|
|
1373
1297
|
const resolved = resolveCliModel({ cliModel: ctx.config.model, modelRegistry });
|
|
@@ -1382,22 +1306,89 @@ async function defaultCreatePiSession(ctx) {
|
|
|
1382
1306
|
parameters: { type: "object", properties: { summary: { type: "string" } }, required: ["summary"] },
|
|
1383
1307
|
execute: async () => ({ content: [{ type: "text", text: "Stage marked complete." }], details: {} })
|
|
1384
1308
|
});
|
|
1309
|
+
let askHandler;
|
|
1310
|
+
const askUserQuestion = defineTool({
|
|
1311
|
+
name: PI_ASK_USER_QUESTION_TOOL,
|
|
1312
|
+
label: "Ask the user a question",
|
|
1313
|
+
description: "Ask the human a structured multiple-choice question and wait for their selection. Use this when you need the human to choose between options before you can continue.",
|
|
1314
|
+
parameters: {
|
|
1315
|
+
type: "object",
|
|
1316
|
+
properties: {
|
|
1317
|
+
questions: {
|
|
1318
|
+
type: "array",
|
|
1319
|
+
items: {
|
|
1320
|
+
type: "object",
|
|
1321
|
+
properties: {
|
|
1322
|
+
question: { type: "string" },
|
|
1323
|
+
options: {
|
|
1324
|
+
type: "array",
|
|
1325
|
+
items: {
|
|
1326
|
+
type: "object",
|
|
1327
|
+
properties: { label: { type: "string" }, description: { type: "string" } },
|
|
1328
|
+
required: ["label"]
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
multiSelect: { type: "boolean" }
|
|
1332
|
+
},
|
|
1333
|
+
required: ["question", "options"]
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
},
|
|
1337
|
+
required: ["questions"]
|
|
1338
|
+
},
|
|
1339
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1340
|
+
execute: async (_toolCallId, params) => {
|
|
1341
|
+
const text = askHandler ? await askHandler(params.questions) : "No response from the user; proceed with your best judgement.";
|
|
1342
|
+
return { content: [{ type: "text", text }], details: {} };
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
let toolCallGate;
|
|
1346
|
+
const toolGateExtension = {
|
|
1347
|
+
name: "dahrk-tool-gate",
|
|
1348
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1349
|
+
factory: (pi) => {
|
|
1350
|
+
pi.on("tool_call", (event) => toolCallGate?.(event.toolName, event.input));
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
const cwd = ctx.workspace.worktreePath;
|
|
1354
|
+
const agentDir = getAgentDir();
|
|
1355
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
1356
|
+
const brokeredMcp = buildBrokeredPiMcpServers(ctx);
|
|
1357
|
+
const extensionFactories = brokeredMcp ? [toolGateExtension, createBrokeredMcpExtension(brokeredMcp)] : [toolGateExtension];
|
|
1358
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
1359
|
+
cwd,
|
|
1360
|
+
agentDir,
|
|
1361
|
+
settingsManager,
|
|
1362
|
+
extensionFactories
|
|
1363
|
+
});
|
|
1364
|
+
await resourceLoader.reload();
|
|
1385
1365
|
const { session } = await createAgentSession({
|
|
1386
1366
|
sessionManager: SessionManager.inMemory(ctx.workspace.worktreePath),
|
|
1387
1367
|
authStorage,
|
|
1388
1368
|
modelRegistry,
|
|
1389
|
-
|
|
1390
|
-
|
|
1369
|
+
settingsManager,
|
|
1370
|
+
resourceLoader,
|
|
1371
|
+
cwd,
|
|
1372
|
+
customTools: [stageComplete, askUserQuestion],
|
|
1391
1373
|
...model ? { model } : {}
|
|
1392
1374
|
});
|
|
1393
|
-
|
|
1375
|
+
const piSession = session;
|
|
1376
|
+
const innerDispose = piSession.dispose.bind(piSession);
|
|
1377
|
+
piSession.dispose = () => {
|
|
1378
|
+
try {
|
|
1379
|
+
innerDispose();
|
|
1380
|
+
} finally {
|
|
1381
|
+
cleanupStageConfigDir(configDir);
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
piSession.setAskUserQuestionHandler = (handler) => {
|
|
1385
|
+
askHandler = handler;
|
|
1386
|
+
};
|
|
1387
|
+
piSession.setToolCallGate = (gate) => {
|
|
1388
|
+
toolCallGate = gate;
|
|
1389
|
+
};
|
|
1390
|
+
return piSession;
|
|
1394
1391
|
}
|
|
1395
|
-
var PROVIDER_BY_ENV = {
|
|
1396
|
-
ANTHROPIC_API_KEY: "anthropic",
|
|
1397
|
-
OPENAI_API_KEY: "openai",
|
|
1398
|
-
GEMINI_API_KEY: "google",
|
|
1399
|
-
GOOGLE_API_KEY: "google"
|
|
1400
|
-
};
|
|
1401
1392
|
function modelFamily(id) {
|
|
1402
1393
|
const last = id.split(".").pop() ?? id;
|
|
1403
1394
|
return last.replace(/-v\d+:\d+$/, "").toLowerCase();
|
|
@@ -1410,15 +1401,236 @@ function pickAuthedModel(resolved, available) {
|
|
|
1410
1401
|
return available.find((m) => modelFamily(m.id) === family) ?? resolved;
|
|
1411
1402
|
}
|
|
1412
1403
|
|
|
1404
|
+
// ../../packages/executor-worktree/src/pi-container.ts
|
|
1405
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
1406
|
+
|
|
1407
|
+
// ../../packages/executor-worktree/src/pi-rpc-client.ts
|
|
1408
|
+
import { StringDecoder } from "string_decoder";
|
|
1409
|
+
function createLineDecoder() {
|
|
1410
|
+
const decoder = new StringDecoder("utf8");
|
|
1411
|
+
let buffer = "";
|
|
1412
|
+
const drain = () => {
|
|
1413
|
+
const lines = [];
|
|
1414
|
+
for (; ; ) {
|
|
1415
|
+
const nl = buffer.indexOf("\n");
|
|
1416
|
+
if (nl === -1) break;
|
|
1417
|
+
let line = buffer.slice(0, nl);
|
|
1418
|
+
buffer = buffer.slice(nl + 1);
|
|
1419
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
1420
|
+
lines.push(line);
|
|
1421
|
+
}
|
|
1422
|
+
return lines;
|
|
1423
|
+
};
|
|
1424
|
+
return {
|
|
1425
|
+
push(chunk) {
|
|
1426
|
+
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
1427
|
+
return drain();
|
|
1428
|
+
},
|
|
1429
|
+
end() {
|
|
1430
|
+
buffer += decoder.end();
|
|
1431
|
+
const lines = drain();
|
|
1432
|
+
if (buffer.length > 0) {
|
|
1433
|
+
const last = buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer;
|
|
1434
|
+
buffer = "";
|
|
1435
|
+
lines.push(last);
|
|
1436
|
+
}
|
|
1437
|
+
return lines;
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function deferred() {
|
|
1442
|
+
let resolve3;
|
|
1443
|
+
let reject;
|
|
1444
|
+
const promise = new Promise((res, rej) => {
|
|
1445
|
+
resolve3 = res;
|
|
1446
|
+
reject = rej;
|
|
1447
|
+
});
|
|
1448
|
+
return { promise, resolve: resolve3, reject };
|
|
1449
|
+
}
|
|
1450
|
+
var isResponse = (msg) => typeof msg === "object" && msg !== null && msg.type === "response";
|
|
1451
|
+
var PiRpcSession = class {
|
|
1452
|
+
#sessionId;
|
|
1453
|
+
#listeners = [];
|
|
1454
|
+
/** Command responses awaited by id (correlated via the optional `id` field). */
|
|
1455
|
+
#pendingResponses = /* @__PURE__ */ new Map();
|
|
1456
|
+
/** The in-flight `prompt()` resolver, settled on the next `agent_end`. */
|
|
1457
|
+
#pendingAgentEnd;
|
|
1458
|
+
#reqCounter = 0;
|
|
1459
|
+
#disposed = false;
|
|
1460
|
+
#child;
|
|
1461
|
+
#kill;
|
|
1462
|
+
constructor(child, options = {}) {
|
|
1463
|
+
this.#child = child;
|
|
1464
|
+
this.#kill = options.kill;
|
|
1465
|
+
const decoder = createLineDecoder();
|
|
1466
|
+
const onData = (chunk) => {
|
|
1467
|
+
for (const line of decoder.push(chunk)) if (line.length > 0) this.#onLine(line);
|
|
1468
|
+
};
|
|
1469
|
+
child.stdout?.on("data", onData);
|
|
1470
|
+
child.stdout?.on("end", () => {
|
|
1471
|
+
for (const line of decoder.end()) if (line.length > 0) this.#onLine(line);
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
get sessionId() {
|
|
1475
|
+
return this.#sessionId;
|
|
1476
|
+
}
|
|
1477
|
+
subscribe(listener) {
|
|
1478
|
+
this.#listeners.push(listener);
|
|
1479
|
+
return () => {
|
|
1480
|
+
this.#listeners = this.#listeners.filter((l) => l !== listener);
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
async prompt(text) {
|
|
1484
|
+
if (this.#disposed) throw new Error("pi rpc session disposed");
|
|
1485
|
+
const agentEnd = deferred();
|
|
1486
|
+
this.#pendingAgentEnd = agentEnd;
|
|
1487
|
+
const ack = await this.#send("prompt", { message: text });
|
|
1488
|
+
if (ack.success === false) {
|
|
1489
|
+
this.#pendingAgentEnd = void 0;
|
|
1490
|
+
throw new Error(ack.error ?? "prompt rejected");
|
|
1491
|
+
}
|
|
1492
|
+
await agentEnd.promise;
|
|
1493
|
+
}
|
|
1494
|
+
async abort() {
|
|
1495
|
+
if (this.#disposed) return;
|
|
1496
|
+
await this.#send("abort", {});
|
|
1497
|
+
}
|
|
1498
|
+
/** Best-effort refresh of the resume token from `get_state`; swallows a failed lookup. */
|
|
1499
|
+
async getState() {
|
|
1500
|
+
if (this.#disposed) return;
|
|
1501
|
+
try {
|
|
1502
|
+
const res = await this.#send("get_state", {});
|
|
1503
|
+
const id = res.data?.sessionId;
|
|
1504
|
+
if (typeof id === "string" && id) this.#sessionId = id;
|
|
1505
|
+
} catch {
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
dispose() {
|
|
1509
|
+
if (this.#disposed) return;
|
|
1510
|
+
this.#disposed = true;
|
|
1511
|
+
try {
|
|
1512
|
+
this.#child.stdin?.end();
|
|
1513
|
+
} catch {
|
|
1514
|
+
}
|
|
1515
|
+
const err = new Error("pi rpc session disposed");
|
|
1516
|
+
for (const d of this.#pendingResponses.values()) d.reject(err);
|
|
1517
|
+
this.#pendingResponses.clear();
|
|
1518
|
+
if (this.#pendingAgentEnd) {
|
|
1519
|
+
this.#pendingAgentEnd.reject(err);
|
|
1520
|
+
this.#pendingAgentEnd = void 0;
|
|
1521
|
+
}
|
|
1522
|
+
if (this.#kill) {
|
|
1523
|
+
const kill = this.#kill;
|
|
1524
|
+
this.#kill = void 0;
|
|
1525
|
+
void kill();
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
/** Write a command as one LF-terminated JSON line and await its correlated response. */
|
|
1529
|
+
#send(type, fields) {
|
|
1530
|
+
const id = `req-${++this.#reqCounter}`;
|
|
1531
|
+
const d = deferred();
|
|
1532
|
+
this.#pendingResponses.set(id, d);
|
|
1533
|
+
try {
|
|
1534
|
+
this.#child.stdin?.write(`${JSON.stringify({ id, type, ...fields })}
|
|
1535
|
+
`);
|
|
1536
|
+
} catch (e) {
|
|
1537
|
+
this.#pendingResponses.delete(id);
|
|
1538
|
+
d.reject(e);
|
|
1539
|
+
}
|
|
1540
|
+
return d.promise;
|
|
1541
|
+
}
|
|
1542
|
+
#onLine(line) {
|
|
1543
|
+
let msg;
|
|
1544
|
+
try {
|
|
1545
|
+
msg = JSON.parse(line);
|
|
1546
|
+
} catch {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
if (isResponse(msg)) {
|
|
1550
|
+
const id = msg.id;
|
|
1551
|
+
if (typeof id === "string") {
|
|
1552
|
+
const pending = this.#pendingResponses.get(id);
|
|
1553
|
+
if (pending) {
|
|
1554
|
+
this.#pendingResponses.delete(id);
|
|
1555
|
+
pending.resolve(msg);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
const ev = msg;
|
|
1561
|
+
for (const l of [...this.#listeners]) l(ev);
|
|
1562
|
+
if (ev.type === "agent_end" && this.#pendingAgentEnd) {
|
|
1563
|
+
const d = this.#pendingAgentEnd;
|
|
1564
|
+
this.#pendingAgentEnd = void 0;
|
|
1565
|
+
d.resolve();
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
// ../../packages/executor-worktree/src/pi-container.ts
|
|
1571
|
+
var DEFAULT_IMAGE = process.env.DAHRK_PI_IMAGE ?? process.env.SKAKEL_PI_IMAGE ?? "dahrk/pi:latest";
|
|
1572
|
+
var _seq = 0;
|
|
1573
|
+
function createContainerPiSession(opts = {}) {
|
|
1574
|
+
const {
|
|
1575
|
+
image = DEFAULT_IMAGE,
|
|
1576
|
+
scratchDir: optsScratchDir,
|
|
1577
|
+
spawn: spawnFn = nodeSpawn,
|
|
1578
|
+
onStderr = (line) => void process.stderr.write(`pi-container: ${line}
|
|
1579
|
+
`)
|
|
1580
|
+
} = opts;
|
|
1581
|
+
return async (ctx) => {
|
|
1582
|
+
const containerName = `dahrk-pi-${Date.now()}-${++_seq}`;
|
|
1583
|
+
const resolvedScratchDir = optsScratchDir ?? ctx.workspace.scratchPath;
|
|
1584
|
+
const mountArgs = resolvedScratchDir ? ["-v", `${resolvedScratchDir}:/dahrk/scratch`] : [];
|
|
1585
|
+
const envArgs = [];
|
|
1586
|
+
for (const [k, v] of Object.entries(ctx.runtimeEnv ?? {})) {
|
|
1587
|
+
envArgs.push("-e", `${k}=${v}`);
|
|
1588
|
+
}
|
|
1589
|
+
const child = spawnFn(
|
|
1590
|
+
"docker",
|
|
1591
|
+
["run", "-i", "--rm", "--name", containerName, ...mountArgs, ...envArgs, image, "pi", "--mode", "rpc"],
|
|
1592
|
+
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
1593
|
+
);
|
|
1594
|
+
child.stderr?.setEncoding("utf8");
|
|
1595
|
+
let stderrBuf = "";
|
|
1596
|
+
child.stderr?.on("data", (chunk) => {
|
|
1597
|
+
stderrBuf += chunk;
|
|
1598
|
+
const lines = stderrBuf.split("\n");
|
|
1599
|
+
stderrBuf = lines.pop() ?? "";
|
|
1600
|
+
for (const line of lines) if (line.trim()) onStderr(line);
|
|
1601
|
+
});
|
|
1602
|
+
child.stderr?.on("end", () => {
|
|
1603
|
+
if (stderrBuf.trim()) onStderr(stderrBuf);
|
|
1604
|
+
stderrBuf = "";
|
|
1605
|
+
});
|
|
1606
|
+
const killContainer = () => new Promise((resolve3) => {
|
|
1607
|
+
const killer = spawnFn("docker", ["kill", containerName], { stdio: "ignore" });
|
|
1608
|
+
killer.on("exit", () => resolve3());
|
|
1609
|
+
killer.on("error", () => resolve3());
|
|
1610
|
+
});
|
|
1611
|
+
return new PiRpcSession(child, { kill: killContainer });
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
function createIsolatedPiRunner(opts = {}) {
|
|
1615
|
+
return createPiRunner({ createSession: createContainerPiSession(opts) });
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1413
1618
|
// ../../packages/executor-worktree/src/git-service.ts
|
|
1414
1619
|
import { execFileSync } from "child_process";
|
|
1415
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
1416
|
-
import { homedir as homedir2, tmpdir as
|
|
1417
|
-
import { basename, dirname, isAbsolute, join as
|
|
1620
|
+
import { existsSync, mkdirSync, mkdtempSync as mkdtempSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1621
|
+
import { homedir as homedir2, tmpdir as tmpdir3 } from "os";
|
|
1622
|
+
import { basename, dirname, isAbsolute, join as join5 } from "path";
|
|
1418
1623
|
var noopLogger = { info: () => {
|
|
1419
1624
|
}, warn: () => {
|
|
1420
1625
|
} };
|
|
1421
|
-
var SCRATCH_DIR = ".
|
|
1626
|
+
var SCRATCH_DIR = ".dahrk/scratch";
|
|
1627
|
+
var DEFAULT_MERGE_RESOLVE_RULES = [
|
|
1628
|
+
{ path: "pnpm-lock.yaml", strategy: "theirs" },
|
|
1629
|
+
{ path: "package-lock.json", strategy: "theirs" },
|
|
1630
|
+
{ path: "yarn.lock", strategy: "theirs" },
|
|
1631
|
+
{ path: "CHANGELOG.md", strategy: "union" },
|
|
1632
|
+
{ path: "CHANGELOG.internal.md", strategy: "union" }
|
|
1633
|
+
];
|
|
1422
1634
|
function parseOwnerRepo(gitUrl) {
|
|
1423
1635
|
const m = /[:/]([^/:]+)\/([^/]+?)(?:\.git)?$/.exec(gitUrl.trim());
|
|
1424
1636
|
return m ? `${m[1]}/${m[2]}` : void 0;
|
|
@@ -1428,10 +1640,10 @@ function sanitizeBranchName(name) {
|
|
|
1428
1640
|
return name.replace(/[`~^:?*[\]\\@{}\s]/g, "-").replace(/\.{2,}/g, ".").replace(/\/{2,}/g, "/").replace(/\.lock(\/|$)/g, "$1").replace(/^[.\-/]+/, "").replace(/[.\-/]+$/, "").replace(/-{2,}/g, "-");
|
|
1429
1641
|
}
|
|
1430
1642
|
function resolveWorktreesDir(override) {
|
|
1431
|
-
return override ?? process.env.DAHRK_WORKTREES_DIR ?? process.env.SKAKEL_WORKTREES_DIR ??
|
|
1643
|
+
return override ?? process.env.DAHRK_WORKTREES_DIR ?? process.env.SKAKEL_WORKTREES_DIR ?? join5(homedir2(), ".dahrk", "worktrees");
|
|
1432
1644
|
}
|
|
1433
1645
|
function resolveMirrorsDir(override) {
|
|
1434
|
-
return override ?? process.env.DAHRK_MIRRORS_DIR ?? process.env.SKAKEL_MIRRORS_DIR ??
|
|
1646
|
+
return override ?? process.env.DAHRK_MIRRORS_DIR ?? process.env.SKAKEL_MIRRORS_DIR ?? join5(homedir2(), ".dahrk", "mirrors");
|
|
1435
1647
|
}
|
|
1436
1648
|
function createGitService(opts = {}) {
|
|
1437
1649
|
const worktreesDir = resolveWorktreesDir(opts.worktreesDir);
|
|
@@ -1467,15 +1679,17 @@ function createGitService(opts = {}) {
|
|
|
1467
1679
|
return (stderr?.trim() || err?.message || String(e)).split("\n")[0] ?? String(e);
|
|
1468
1680
|
};
|
|
1469
1681
|
const excludeScratchLocally = (worktreePath) => {
|
|
1470
|
-
const
|
|
1682
|
+
const entries = [`${SCRATCH_DIR}/`];
|
|
1471
1683
|
try {
|
|
1472
1684
|
const rel = git(worktreePath, ["rev-parse", "--git-path", "info/exclude"]).trim();
|
|
1473
|
-
const excludePath = isAbsolute(rel) ? rel :
|
|
1685
|
+
const excludePath = isAbsolute(rel) ? rel : join5(worktreePath, rel);
|
|
1474
1686
|
const existing = existsSync(excludePath) ? readFileSync2(excludePath, "utf-8") : "";
|
|
1475
|
-
|
|
1687
|
+
const lines = existing.split("\n").map((l) => l.trim());
|
|
1688
|
+
const toAdd = entries.filter((e) => !lines.includes(e));
|
|
1689
|
+
if (toAdd.length === 0) return;
|
|
1476
1690
|
mkdirSync(dirname(excludePath), { recursive: true });
|
|
1477
1691
|
const sep4 = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
1478
|
-
|
|
1692
|
+
writeFileSync2(excludePath, `${existing}${sep4}${toAdd.join("\n")}
|
|
1479
1693
|
`);
|
|
1480
1694
|
} catch (e) {
|
|
1481
1695
|
log.warn(`could not set worktree scratch exclude at ${worktreePath}: ${e.message}`);
|
|
@@ -1499,13 +1713,53 @@ function createGitService(opts = {}) {
|
|
|
1499
1713
|
}
|
|
1500
1714
|
return { headSha: git(worktreePath, ["rev-parse", "HEAD"]).trim(), dirty };
|
|
1501
1715
|
};
|
|
1716
|
+
const unmergedPaths = (worktreePath) => git(worktreePath, ["diff", "--name-only", "--diff-filter=U"]).split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1717
|
+
const unionMerge = (worktreePath, p) => {
|
|
1718
|
+
const stage = (n) => {
|
|
1719
|
+
try {
|
|
1720
|
+
return git(worktreePath, ["show", `:${n}:${p}`]);
|
|
1721
|
+
} catch {
|
|
1722
|
+
return "";
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
const dir = mkdtempSync2(join5(tmpdir3(), "dahrk-union-"));
|
|
1726
|
+
try {
|
|
1727
|
+
const ours = join5(dir, "ours");
|
|
1728
|
+
const base = join5(dir, "base");
|
|
1729
|
+
const theirs = join5(dir, "theirs");
|
|
1730
|
+
writeFileSync2(ours, stage(2));
|
|
1731
|
+
writeFileSync2(base, stage(1));
|
|
1732
|
+
writeFileSync2(theirs, stage(3));
|
|
1733
|
+
const merged = git(worktreePath, ["merge-file", "-p", "--union", ours, base, theirs]);
|
|
1734
|
+
writeFileSync2(join5(worktreePath, p), merged);
|
|
1735
|
+
} finally {
|
|
1736
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
const preResolveConflicts = (worktreePath, rules) => {
|
|
1740
|
+
gitOk2(worktreePath, ["rerere"]);
|
|
1741
|
+
for (const p of unmergedPaths(worktreePath)) {
|
|
1742
|
+
const rule = rules.find((r) => r.path === p || basename(p) === r.path);
|
|
1743
|
+
if (!rule) continue;
|
|
1744
|
+
try {
|
|
1745
|
+
if (rule.strategy === "union") {
|
|
1746
|
+
unionMerge(worktreePath, p);
|
|
1747
|
+
} else {
|
|
1748
|
+
git(worktreePath, ["checkout", `--${rule.strategy}`, "--", p]);
|
|
1749
|
+
}
|
|
1750
|
+
git(worktreePath, ["add", "--", p]);
|
|
1751
|
+
} catch (e) {
|
|
1752
|
+
log.warn(`pre-resolve of ${p} (${rule.strategy}) failed: ${e.message}`);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1502
1756
|
const setupAuth = (token) => {
|
|
1503
|
-
const dir =
|
|
1504
|
-
const script =
|
|
1505
|
-
|
|
1757
|
+
const dir = mkdtempSync2(join5(tmpdir3(), "dahrk-cred-"));
|
|
1758
|
+
const script = join5(dir, "askpass.sh");
|
|
1759
|
+
writeFileSync2(script, '#!/bin/sh\nprintf "%s" "$DAHRK_GIT_TOKEN"\n', { mode: 448 });
|
|
1506
1760
|
return {
|
|
1507
1761
|
env: { ...process.env, GIT_ASKPASS: script, DAHRK_GIT_TOKEN: token, GIT_TERMINAL_PROMPT: "0" },
|
|
1508
|
-
cleanup: () =>
|
|
1762
|
+
cleanup: () => rmSync2(dir, { recursive: true, force: true })
|
|
1509
1763
|
};
|
|
1510
1764
|
};
|
|
1511
1765
|
const netEnv = (authEnv) => authEnv ?? { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
@@ -1518,7 +1772,7 @@ function createGitService(opts = {}) {
|
|
|
1518
1772
|
cleanup: () => auth?.cleanup()
|
|
1519
1773
|
};
|
|
1520
1774
|
};
|
|
1521
|
-
const mirrorPathFor = (repoId) =>
|
|
1775
|
+
const mirrorPathFor = (repoId) => join5(mirrorsDir, sanitizeBranchName(repoId));
|
|
1522
1776
|
const listWorktrees = (mirror) => {
|
|
1523
1777
|
let out2;
|
|
1524
1778
|
try {
|
|
@@ -1543,7 +1797,7 @@ function createGitService(opts = {}) {
|
|
|
1543
1797
|
} catch (e) {
|
|
1544
1798
|
log.warn(`git worktree remove failed for ${worktreePath}: ${e.message}`);
|
|
1545
1799
|
}
|
|
1546
|
-
|
|
1800
|
+
rmSync2(worktreePath, { recursive: true, force: true });
|
|
1547
1801
|
gitOk2(mirror, ["worktree", "prune"]);
|
|
1548
1802
|
};
|
|
1549
1803
|
const TRACKING_REFSPEC = "+refs/heads/*:refs/remotes/origin/*";
|
|
@@ -1630,18 +1884,19 @@ function createGitService(opts = {}) {
|
|
|
1630
1884
|
baseBranch: spec.baseBranch,
|
|
1631
1885
|
branch: sanitizeBranchName(spec.branch ?? `dahrk/${spec.runId}`),
|
|
1632
1886
|
worktreePath,
|
|
1633
|
-
scratchPath:
|
|
1887
|
+
scratchPath: join5(worktreePath, SCRATCH_DIR)
|
|
1634
1888
|
});
|
|
1635
1889
|
return {
|
|
1636
1890
|
worktreesDir,
|
|
1637
1891
|
async createWorktree(spec) {
|
|
1638
1892
|
const { repoId, gitUrl, baseBranch, runId } = spec;
|
|
1639
1893
|
const branchName = sanitizeBranchName(spec.branch ?? `dahrk/${runId}`);
|
|
1640
|
-
const worktreePath =
|
|
1894
|
+
const worktreePath = join5(worktreesDir, runId);
|
|
1641
1895
|
mkdirSync(worktreesDir, { recursive: true });
|
|
1642
1896
|
if (existsSync(worktreePath) && gitOk2(worktreePath, ["rev-parse", "--git-dir"])) {
|
|
1643
1897
|
log.info(`reusing existing worktree at ${worktreePath}`);
|
|
1644
|
-
mkdirSync(
|
|
1898
|
+
mkdirSync(join5(worktreePath, SCRATCH_DIR), { recursive: true });
|
|
1899
|
+
excludeScratchLocally(worktreePath);
|
|
1645
1900
|
return refFor(spec, worktreePath);
|
|
1646
1901
|
}
|
|
1647
1902
|
const auth = spec.credentialToken ? setupAuth(spec.credentialToken) : void 0;
|
|
@@ -1677,7 +1932,8 @@ function createGitService(opts = {}) {
|
|
|
1677
1932
|
if (!gitOk2(worktreePath, ["rev-parse", "--verify", "-q", "HEAD"])) {
|
|
1678
1933
|
throw new Error(`base '${baseBranch}' did not materialise into ${worktreePath} (unborn HEAD)`);
|
|
1679
1934
|
}
|
|
1680
|
-
mkdirSync(
|
|
1935
|
+
mkdirSync(join5(worktreePath, SCRATCH_DIR), { recursive: true });
|
|
1936
|
+
excludeScratchLocally(worktreePath);
|
|
1681
1937
|
return refFor(spec, worktreePath);
|
|
1682
1938
|
},
|
|
1683
1939
|
async commitAndPush(ref, opts2) {
|
|
@@ -1725,6 +1981,10 @@ function createGitService(opts = {}) {
|
|
|
1725
1981
|
`user.name=${authorName}`,
|
|
1726
1982
|
"-c",
|
|
1727
1983
|
`user.email=${authorEmail}`,
|
|
1984
|
+
"-c",
|
|
1985
|
+
"rerere.enabled=true",
|
|
1986
|
+
"-c",
|
|
1987
|
+
"rerere.autoupdate=true",
|
|
1728
1988
|
"merge",
|
|
1729
1989
|
"--no-edit",
|
|
1730
1990
|
"FETCH_HEAD"
|
|
@@ -1734,15 +1994,30 @@ function createGitService(opts = {}) {
|
|
|
1734
1994
|
} catch (mergeErr) {
|
|
1735
1995
|
const inMerge = gitOk2(worktreePath, ["rev-parse", "--verify", "-q", "MERGE_HEAD"]);
|
|
1736
1996
|
if (inMerge) {
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1997
|
+
preResolveConflicts(worktreePath, opts2.mergeResolve ?? DEFAULT_MERGE_RESOLVE_RULES);
|
|
1998
|
+
const conflictFiles = unmergedPaths(worktreePath);
|
|
1999
|
+
if (conflictFiles.length === 0) {
|
|
2000
|
+
git(worktreePath, [
|
|
2001
|
+
"-c",
|
|
2002
|
+
`user.name=${authorName}`,
|
|
2003
|
+
"-c",
|
|
2004
|
+
`user.email=${authorEmail}`,
|
|
2005
|
+
"commit",
|
|
2006
|
+
"--no-edit"
|
|
2007
|
+
]);
|
|
2008
|
+
integration = "clean";
|
|
2009
|
+
headSha = git(worktreePath, ["rev-parse", "HEAD"]).trim();
|
|
2010
|
+
} else {
|
|
2011
|
+
git(worktreePath, ["merge", "--abort"]);
|
|
2012
|
+
return { headSha, pushed: false, nothingToCommit: !dirty, commitsAhead, integration: "conflict", conflictFiles };
|
|
2013
|
+
}
|
|
2014
|
+
} else {
|
|
2015
|
+
const msg = mergeErr.message;
|
|
2016
|
+
if (/unrelated histories|refusing to merge/i.test(msg)) {
|
|
2017
|
+
return { headSha, pushed: false, nothingToCommit: !dirty, commitsAhead, integration: "diverged" };
|
|
2018
|
+
}
|
|
2019
|
+
throw mergeErr;
|
|
1744
2020
|
}
|
|
1745
|
-
throw mergeErr;
|
|
1746
2021
|
}
|
|
1747
2022
|
}
|
|
1748
2023
|
git(worktreePath, ["push", remote, `HEAD:refs/heads/${branch}`], netEnv(authEnv));
|
|
@@ -1805,10 +2080,10 @@ function createGitService(opts = {}) {
|
|
|
1805
2080
|
return void 0;
|
|
1806
2081
|
}
|
|
1807
2082
|
};
|
|
1808
|
-
const tmp =
|
|
1809
|
-
const bodyFile =
|
|
2083
|
+
const tmp = mkdtempSync2(join5(tmpdir3(), "dahrk-pr-"));
|
|
2084
|
+
const bodyFile = join5(tmp, "body.md");
|
|
1810
2085
|
try {
|
|
1811
|
-
|
|
2086
|
+
writeFileSync2(bodyFile, opts2.body ?? "");
|
|
1812
2087
|
try {
|
|
1813
2088
|
gh(worktreePath, [
|
|
1814
2089
|
"pr",
|
|
@@ -1830,7 +2105,7 @@ function createGitService(opts = {}) {
|
|
|
1830
2105
|
}
|
|
1831
2106
|
return readBack() ?? { prError: "gh pr create reported success but no PR was found" };
|
|
1832
2107
|
} finally {
|
|
1833
|
-
|
|
2108
|
+
rmSync2(tmp, { recursive: true, force: true });
|
|
1834
2109
|
}
|
|
1835
2110
|
},
|
|
1836
2111
|
async teardownWorktree(ref) {
|
|
@@ -1841,8 +2116,8 @@ function createGitService(opts = {}) {
|
|
|
1841
2116
|
|
|
1842
2117
|
// ../../packages/executor-worktree/src/worktree-reaper.ts
|
|
1843
2118
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1844
|
-
import { existsSync as existsSync2, readdirSync, realpathSync, rmSync as
|
|
1845
|
-
import { join as
|
|
2119
|
+
import { existsSync as existsSync2, readdirSync, realpathSync, rmSync as rmSync3, statSync } from "fs";
|
|
2120
|
+
import { join as join6 } from "path";
|
|
1846
2121
|
var MINUTE = 6e4;
|
|
1847
2122
|
var HOUR = 60 * MINUTE;
|
|
1848
2123
|
var DEFAULTS = {
|
|
@@ -1872,7 +2147,7 @@ var canonical = (p) => {
|
|
|
1872
2147
|
}
|
|
1873
2148
|
};
|
|
1874
2149
|
function lastUsedMs(worktreePath) {
|
|
1875
|
-
const candidates = [
|
|
2150
|
+
const candidates = [join6(worktreePath, ".dahrk", "scratch", "state.json"), worktreePath];
|
|
1876
2151
|
let newest = 0;
|
|
1877
2152
|
for (const p of candidates) {
|
|
1878
2153
|
try {
|
|
@@ -1895,7 +2170,7 @@ function createWorktreeReaper(opts) {
|
|
|
1895
2170
|
const log = opts.logger ?? noop;
|
|
1896
2171
|
const mirrors = () => {
|
|
1897
2172
|
try {
|
|
1898
|
-
return readdirSync(opts.mirrorsDir).map((d) =>
|
|
2173
|
+
return readdirSync(opts.mirrorsDir).map((d) => join6(opts.mirrorsDir, d)).filter((m) => gitOk(m, ["rev-parse", "--git-dir"]));
|
|
1899
2174
|
} catch {
|
|
1900
2175
|
return [];
|
|
1901
2176
|
}
|
|
@@ -1919,7 +2194,7 @@ function createWorktreeReaper(opts) {
|
|
|
1919
2194
|
}
|
|
1920
2195
|
const onDisk = (() => {
|
|
1921
2196
|
try {
|
|
1922
|
-
return readdirSync(opts.worktreesDir).map((d) => canonical(
|
|
2197
|
+
return readdirSync(opts.worktreesDir).map((d) => canonical(join6(opts.worktreesDir, d)));
|
|
1923
2198
|
} catch {
|
|
1924
2199
|
return [];
|
|
1925
2200
|
}
|
|
@@ -1962,7 +2237,7 @@ function createWorktreeReaper(opts) {
|
|
|
1962
2237
|
if (d.mirror) {
|
|
1963
2238
|
gitOk(d.mirror, ["worktree", "remove", "--force", d.path]);
|
|
1964
2239
|
}
|
|
1965
|
-
|
|
2240
|
+
rmSync3(d.path, { recursive: true, force: true });
|
|
1966
2241
|
if (d.mirror) gitOk(d.mirror, ["worktree", "prune"]);
|
|
1967
2242
|
report.reaped.push({ runId: d.runId, path: d.path, reason: d.reason });
|
|
1968
2243
|
} catch (e) {
|
|
@@ -1979,20 +2254,20 @@ function createWorktreeReaper(opts) {
|
|
|
1979
2254
|
|
|
1980
2255
|
// ../../packages/executor-worktree/src/trace-writer.ts
|
|
1981
2256
|
import { createHash } from "crypto";
|
|
1982
|
-
import { appendFileSync, mkdirSync as mkdirSync2, writeFileSync as
|
|
1983
|
-
import { join as
|
|
2257
|
+
import { appendFileSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2258
|
+
import { join as join7 } from "path";
|
|
1984
2259
|
var DEFAULT_SPILL_BYTES = 8192;
|
|
1985
2260
|
function createTraceWriter(scratchPath, meta, opts = {}) {
|
|
1986
2261
|
const spillBytes = opts.spillBytes ?? DEFAULT_SPILL_BYTES;
|
|
1987
|
-
const dir =
|
|
1988
|
-
mkdirSync2(
|
|
1989
|
-
mkdirSync2(
|
|
1990
|
-
const tracePath =
|
|
1991
|
-
const metaPath =
|
|
2262
|
+
const dir = join7(scratchPath, "traces", meta.stageId, `attempt-${meta.attempt}`);
|
|
2263
|
+
mkdirSync2(join7(dir, "blobs"), { recursive: true });
|
|
2264
|
+
mkdirSync2(join7(dir, "raw"), { recursive: true });
|
|
2265
|
+
const tracePath = join7(dir, "trace.jsonl");
|
|
2266
|
+
const metaPath = join7(dir, "meta.json");
|
|
1992
2267
|
let current = { ...meta };
|
|
1993
2268
|
let nextSeq = 0;
|
|
1994
2269
|
let rawCount = 0;
|
|
1995
|
-
|
|
2270
|
+
writeFileSync3(metaPath, JSON.stringify(current, null, 2));
|
|
1996
2271
|
const tooBig = (value) => {
|
|
1997
2272
|
const s = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
1998
2273
|
return s.length > spillBytes;
|
|
@@ -2000,8 +2275,8 @@ function createTraceWriter(scratchPath, meta, opts = {}) {
|
|
|
2000
2275
|
const spillValue = (value) => {
|
|
2001
2276
|
const data = typeof value === "string" ? value : JSON.stringify(value);
|
|
2002
2277
|
const sha = createHash("sha256").update(data).digest("hex");
|
|
2003
|
-
|
|
2004
|
-
return
|
|
2278
|
+
writeFileSync3(join7(dir, "blobs", sha), data);
|
|
2279
|
+
return join7("blobs", sha);
|
|
2005
2280
|
};
|
|
2006
2281
|
const spill = (event) => {
|
|
2007
2282
|
if (event.type === "thought" && event.text !== void 0 && tooBig(event.text)) {
|
|
@@ -2031,13 +2306,13 @@ function createTraceWriter(scratchPath, meta, opts = {}) {
|
|
|
2031
2306
|
return written;
|
|
2032
2307
|
},
|
|
2033
2308
|
writeRaw(record) {
|
|
2034
|
-
const rel =
|
|
2035
|
-
|
|
2309
|
+
const rel = join7("raw", `${rawCount++}.json`);
|
|
2310
|
+
writeFileSync3(join7(dir, rel), JSON.stringify(record, null, 2));
|
|
2036
2311
|
return rel;
|
|
2037
2312
|
},
|
|
2038
2313
|
finalise(patch = {}) {
|
|
2039
2314
|
current = { ...current, ...patch };
|
|
2040
|
-
|
|
2315
|
+
writeFileSync3(metaPath, JSON.stringify(current, null, 2));
|
|
2041
2316
|
},
|
|
2042
2317
|
count() {
|
|
2043
2318
|
return nextSeq;
|
|
@@ -2047,13 +2322,13 @@ function createTraceWriter(scratchPath, meta, opts = {}) {
|
|
|
2047
2322
|
|
|
2048
2323
|
// ../../packages/executor-worktree/src/pack-cache.ts
|
|
2049
2324
|
import { createHash as createHash2 } from "crypto";
|
|
2050
|
-
import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, mkdtempSync as
|
|
2051
|
-
import { dirname as dirname2, join as
|
|
2325
|
+
import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync3, readdirSync as readdirSync2, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2326
|
+
import { dirname as dirname2, join as join8, relative, sep } from "path";
|
|
2052
2327
|
function readManifestFiles(dir) {
|
|
2053
2328
|
const out2 = [];
|
|
2054
2329
|
const walk = (cur) => {
|
|
2055
2330
|
for (const entry of readdirSync2(cur, { withFileTypes: true })) {
|
|
2056
|
-
const abs =
|
|
2331
|
+
const abs = join8(cur, entry.name);
|
|
2057
2332
|
if (entry.isDirectory()) walk(abs);
|
|
2058
2333
|
else out2.push(relative(dir, abs).split(sep).join("/"));
|
|
2059
2334
|
}
|
|
@@ -2063,8 +2338,8 @@ function readManifestFiles(dir) {
|
|
|
2063
2338
|
}
|
|
2064
2339
|
|
|
2065
2340
|
// ../../packages/executor-worktree/src/overlay.ts
|
|
2066
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as
|
|
2067
|
-
import { dirname as dirname3, join as
|
|
2341
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
2342
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
2068
2343
|
function sameBytes(dest, bytes) {
|
|
2069
2344
|
try {
|
|
2070
2345
|
return readFileSync3(dest).equals(bytes);
|
|
@@ -2076,16 +2351,16 @@ async function overlayComponents(opts) {
|
|
|
2076
2351
|
const { worktreePath, runtime, components, cache } = opts;
|
|
2077
2352
|
const result = { written: [], skippedRepoLocal: [], warnings: [] };
|
|
2078
2353
|
for (const ref of components) {
|
|
2079
|
-
if (runtime
|
|
2354
|
+
if (runtime !== "claude-code") {
|
|
2080
2355
|
result.warnings.push(
|
|
2081
|
-
|
|
2356
|
+
`${runtime} runtime: ${ref.kind} \`${ref.name}@${ref.version}\` not materialised; inline into the prompt or use Claude`
|
|
2082
2357
|
);
|
|
2083
2358
|
continue;
|
|
2084
2359
|
}
|
|
2085
2360
|
const { dir } = await cache.materialise(ref);
|
|
2086
2361
|
for (const relPath of readManifestFiles(dir)) {
|
|
2087
|
-
const src =
|
|
2088
|
-
const dest =
|
|
2362
|
+
const src = join9(dir, relPath);
|
|
2363
|
+
const dest = join9(worktreePath, relPath);
|
|
2089
2364
|
const bytes = readFileSync3(src);
|
|
2090
2365
|
if (existsSync4(dest)) {
|
|
2091
2366
|
if (sameBytes(dest, bytes)) continue;
|
|
@@ -2093,27 +2368,21 @@ async function overlayComponents(opts) {
|
|
|
2093
2368
|
continue;
|
|
2094
2369
|
}
|
|
2095
2370
|
mkdirSync4(dirname3(dest), { recursive: true });
|
|
2096
|
-
|
|
2371
|
+
writeFileSync5(dest, bytes);
|
|
2097
2372
|
result.written.push(relPath);
|
|
2098
2373
|
}
|
|
2099
2374
|
}
|
|
2100
2375
|
return result;
|
|
2101
2376
|
}
|
|
2102
2377
|
|
|
2103
|
-
// ../../packages/executor-worktree/src/pi-container.ts
|
|
2104
|
-
import { spawn as nodeSpawn } from "child_process";
|
|
2105
|
-
|
|
2106
|
-
// ../../packages/executor-worktree/src/pi-rpc-client.ts
|
|
2107
|
-
import { StringDecoder } from "string_decoder";
|
|
2108
|
-
|
|
2109
|
-
// ../../packages/executor-worktree/src/pi-container.ts
|
|
2110
|
-
var DEFAULT_IMAGE = process.env.DAHRK_PI_IMAGE ?? process.env.SKAKEL_PI_IMAGE ?? "dahrk/pi:latest";
|
|
2111
|
-
|
|
2112
2378
|
// ../../packages/executor-worktree/src/index.ts
|
|
2113
2379
|
function makeRunner(runtime) {
|
|
2114
2380
|
if ((process.env.DAHRK_RUNNER ?? process.env.SKAKEL_RUNNER ?? "real") === "mock") return createMockRunner(runtime);
|
|
2115
|
-
if (runtime === "pi") return createPiRunner();
|
|
2116
|
-
return
|
|
2381
|
+
if (runtime === "pi") return piContainerIsolationRequired() ? createIsolatedPiRunner() : createPiRunner();
|
|
2382
|
+
return createClaudeRunner();
|
|
2383
|
+
}
|
|
2384
|
+
function piContainerIsolationRequired() {
|
|
2385
|
+
return (process.env.DAHRK_PI_ISOLATION ?? process.env.SKAKEL_PI_ISOLATION) === "container";
|
|
2117
2386
|
}
|
|
2118
2387
|
|
|
2119
2388
|
// ../../packages/edge/src/health.ts
|
|
@@ -2172,8 +2441,8 @@ function collectHealth(inputs) {
|
|
|
2172
2441
|
}
|
|
2173
2442
|
|
|
2174
2443
|
// ../../packages/edge/src/job-ledger.ts
|
|
2175
|
-
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, renameSync, unlinkSync, writeFileSync as
|
|
2176
|
-
import { dirname as dirname4, join as
|
|
2444
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, renameSync, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
|
|
2445
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
2177
2446
|
var FILE_MODE = 384;
|
|
2178
2447
|
var DIR_MODE = 448;
|
|
2179
2448
|
function nullJobLedger() {
|
|
@@ -2202,7 +2471,7 @@ function fileJobLedger(file, warn = console.warn) {
|
|
|
2202
2471
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
2203
2472
|
try {
|
|
2204
2473
|
mkdirSync5(dirname4(file), { recursive: true, mode: DIR_MODE });
|
|
2205
|
-
|
|
2474
|
+
writeFileSync6(tmp, `${JSON.stringify(entries, null, 2)}
|
|
2206
2475
|
`, { mode: FILE_MODE });
|
|
2207
2476
|
chmodSync(tmp, FILE_MODE);
|
|
2208
2477
|
renameSync(tmp, file);
|
|
@@ -2237,7 +2506,7 @@ function announceableJobs(entries) {
|
|
|
2237
2506
|
return out2;
|
|
2238
2507
|
}
|
|
2239
2508
|
function jobLedgerFile(stateDir2) {
|
|
2240
|
-
return
|
|
2509
|
+
return join10(stateDir2, "jobs.json");
|
|
2241
2510
|
}
|
|
2242
2511
|
|
|
2243
2512
|
// ../../packages/edge/src/log-shipper.ts
|
|
@@ -2340,8 +2609,8 @@ function ceilingFromEnv(env) {
|
|
|
2340
2609
|
}
|
|
2341
2610
|
|
|
2342
2611
|
// ../../packages/edge/src/logger.ts
|
|
2343
|
-
import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync6, openSync, renameSync as renameSync2, rmSync as
|
|
2344
|
-
import { join as
|
|
2612
|
+
import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync6, openSync, renameSync as renameSync2, rmSync as rmSync5, statSync as statSync2, writeSync } from "fs";
|
|
2613
|
+
import { join as join11 } from "path";
|
|
2345
2614
|
import pino from "pino";
|
|
2346
2615
|
|
|
2347
2616
|
// ../../packages/edge/src/redact.ts
|
|
@@ -2465,7 +2734,7 @@ var RotatingFile = class {
|
|
|
2465
2734
|
closeSync(this.fd);
|
|
2466
2735
|
this.fd = void 0;
|
|
2467
2736
|
const oldest = `${this.path}.${this.maxFiles}`;
|
|
2468
|
-
if (existsSync6(oldest))
|
|
2737
|
+
if (existsSync6(oldest)) rmSync5(oldest, { force: true });
|
|
2469
2738
|
for (let i = this.maxFiles - 1; i >= 1; i--) {
|
|
2470
2739
|
const from = `${this.path}.${i}`;
|
|
2471
2740
|
if (existsSync6(from)) renameSync2(from, `${this.path}.${i + 1}`);
|
|
@@ -2531,7 +2800,7 @@ function createNodeLogger(opts = {}) {
|
|
|
2531
2800
|
if (opts.dir && fileLevel !== "silent") {
|
|
2532
2801
|
try {
|
|
2533
2802
|
mkdirSync6(opts.dir, { recursive: true, mode: 448 });
|
|
2534
|
-
const file = new RotatingFile(
|
|
2803
|
+
const file = new RotatingFile(join11(opts.dir, "node.jsonl"), opts.maxBytes ?? 10 * 1024 * 1024, opts.maxFiles ?? 5);
|
|
2535
2804
|
streams.push({ level: fileLevel, stream: file });
|
|
2536
2805
|
} catch (e) {
|
|
2537
2806
|
process.stderr.write(`dahrk: file logging disabled (${e.message})
|
|
@@ -2589,9 +2858,9 @@ function denyToolRule(tool3) {
|
|
|
2589
2858
|
// ../../packages/edge/src/stage-runner.ts
|
|
2590
2859
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
2591
2860
|
import { createHash as createHash3 } from "crypto";
|
|
2592
|
-
import { mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync5, rmSync as
|
|
2593
|
-
import { tmpdir as
|
|
2594
|
-
import { isAbsolute as isAbsolute3, join as
|
|
2861
|
+
import { mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
2862
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
2863
|
+
import { isAbsolute as isAbsolute3, join as join13, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
|
|
2595
2864
|
import { attachedDocBasename as attachedDocBasename2 } from "@dahrk/contracts";
|
|
2596
2865
|
|
|
2597
2866
|
// ../../packages/edge/src/builtins.ts
|
|
@@ -2600,8 +2869,8 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
2600
2869
|
// ../../packages/edge/src/fs-roots.ts
|
|
2601
2870
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2602
2871
|
import { existsSync as existsSync7, realpathSync as realpathSync2 } from "fs";
|
|
2603
|
-
import { homedir as homedir3, tmpdir as
|
|
2604
|
-
import { dirname as dirname5, isAbsolute as isAbsolute2, join as
|
|
2872
|
+
import { homedir as homedir3, tmpdir as tmpdir4 } from "os";
|
|
2873
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join12, relative as relative2, resolve, sep as sep2 } from "path";
|
|
2605
2874
|
function isUnder(root, target) {
|
|
2606
2875
|
const rel = relative2(resolve(root), resolve(target));
|
|
2607
2876
|
return rel === "" || !rel.startsWith(`..${sep2}`) && rel !== ".." && !isAbsolute2(rel);
|
|
@@ -2617,7 +2886,7 @@ function realish(p) {
|
|
|
2617
2886
|
while (head !== dirname5(head)) {
|
|
2618
2887
|
if (existsSync7(head)) {
|
|
2619
2888
|
try {
|
|
2620
|
-
return
|
|
2889
|
+
return join12(realpathSync2.native(head), ...tail.reverse());
|
|
2621
2890
|
} catch {
|
|
2622
2891
|
return p;
|
|
2623
2892
|
}
|
|
@@ -2667,7 +2936,7 @@ function computeFsRoots(opts) {
|
|
|
2667
2936
|
// Every git command in the worktree reads and writes here.
|
|
2668
2937
|
...[gitCommonDir(opts.worktreePath)].filter((p) => Boolean(p)).map(realish),
|
|
2669
2938
|
// Scratch space: `mkdtemp`, git's temp files, and an agent tidying a throwaway dir it created.
|
|
2670
|
-
...[
|
|
2939
|
+
...[tmpdir4(), "/tmp", "/private/tmp", "/var/folders", "/private/var/folders"].map(realish),
|
|
2671
2940
|
// The safe I/O sinks. `2>/dev/null` is on a third of the shell commands real stages run; treating
|
|
2672
2941
|
// it as a write outside the worktree would deny most of a normal build. The raw devices are NOT
|
|
2673
2942
|
// here, so `> /dev/sda` still fails confinement (as well as shell_guard's device-write regex).
|
|
@@ -2686,26 +2955,26 @@ function computeFsRoots(opts) {
|
|
|
2686
2955
|
"/System",
|
|
2687
2956
|
"/proc",
|
|
2688
2957
|
"/sys",
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2958
|
+
join12(home, ".gitconfig"),
|
|
2959
|
+
join12(home, ".config"),
|
|
2960
|
+
join12(home, ".npmrc"),
|
|
2961
|
+
join12(home, ".cache"),
|
|
2962
|
+
join12(home, "Library", "Caches"),
|
|
2963
|
+
join12(home, "Library", "pnpm"),
|
|
2964
|
+
join12(home, ".local", "share"),
|
|
2965
|
+
join12(home, ".nvm"),
|
|
2966
|
+
join12(home, ".volta"),
|
|
2967
|
+
join12(home, ".asdf"),
|
|
2968
|
+
join12(home, ".cargo"),
|
|
2969
|
+
join12(home, ".rustup"),
|
|
2701
2970
|
...splitRoots(process.env.DAHRK_FS_EXTRA_READ_ROOTS).map((p) => realish(resolve(p)))
|
|
2702
2971
|
].map(realish);
|
|
2703
2972
|
const deny = [
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2973
|
+
join12(home, ".ssh"),
|
|
2974
|
+
join12(home, ".aws"),
|
|
2975
|
+
join12(home, ".gnupg"),
|
|
2976
|
+
join12(home, ".config", "gcloud"),
|
|
2977
|
+
join12(home, "Library", "Keychains"),
|
|
2709
2978
|
"/Volumes",
|
|
2710
2979
|
"/etc/shadow",
|
|
2711
2980
|
"/etc/sudoers"
|
|
@@ -3290,7 +3559,7 @@ var attemptOf = (jobId) => {
|
|
|
3290
3559
|
};
|
|
3291
3560
|
var digest = (value) => `sha256:${createHash3("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16)}`;
|
|
3292
3561
|
function writeScratchState(ref, job, attempt, status) {
|
|
3293
|
-
const statePath =
|
|
3562
|
+
const statePath = join13(ref.scratchPath, "state.json");
|
|
3294
3563
|
let state;
|
|
3295
3564
|
try {
|
|
3296
3565
|
state = JSON.parse(readFileSync5(statePath, "utf8"));
|
|
@@ -3299,23 +3568,23 @@ function writeScratchState(ref, job, attempt, status) {
|
|
|
3299
3568
|
}
|
|
3300
3569
|
state.stages[job.stageId] = { currentAttempt: attempt, status };
|
|
3301
3570
|
mkdirSync7(ref.scratchPath, { recursive: true });
|
|
3302
|
-
|
|
3571
|
+
writeFileSync7(statePath, JSON.stringify(state, null, 2));
|
|
3303
3572
|
}
|
|
3304
3573
|
function writeIssueContext(ref, issueContext) {
|
|
3305
3574
|
if (issueContext === void 0) return;
|
|
3306
3575
|
try {
|
|
3307
3576
|
mkdirSync7(ref.scratchPath, { recursive: true });
|
|
3308
|
-
|
|
3577
|
+
writeFileSync7(join13(ref.scratchPath, "issue.md"), issueContext);
|
|
3309
3578
|
} catch {
|
|
3310
3579
|
}
|
|
3311
3580
|
}
|
|
3312
3581
|
function writeAttachedDocuments(ref, docs) {
|
|
3313
3582
|
if (!docs || docs.length === 0) return;
|
|
3314
3583
|
try {
|
|
3315
|
-
const dir =
|
|
3584
|
+
const dir = join13(ref.scratchPath, "docs");
|
|
3316
3585
|
mkdirSync7(dir, { recursive: true });
|
|
3317
3586
|
for (const doc of docs) {
|
|
3318
|
-
|
|
3587
|
+
writeFileSync7(join13(dir, `${attachedDocBasename2(doc)}.md`), doc.content);
|
|
3319
3588
|
}
|
|
3320
3589
|
} catch {
|
|
3321
3590
|
}
|
|
@@ -3334,12 +3603,12 @@ function writeGuidance(ref, guidance) {
|
|
|
3334
3603
|
if (!guidance || guidance.length === 0) return;
|
|
3335
3604
|
try {
|
|
3336
3605
|
mkdirSync7(ref.scratchPath, { recursive: true });
|
|
3337
|
-
|
|
3606
|
+
writeFileSync7(join13(ref.scratchPath, "guidance.md"), renderGuidanceMarkdown(guidance));
|
|
3338
3607
|
} catch {
|
|
3339
3608
|
}
|
|
3340
3609
|
}
|
|
3341
3610
|
var ARTIFACT_CAP_BYTES = 64 * 1024;
|
|
3342
|
-
var SCRATCH_OUTPUT_DIR = ".
|
|
3611
|
+
var SCRATCH_OUTPUT_DIR = ".dahrk/scratch/output";
|
|
3343
3612
|
function capContent(raw) {
|
|
3344
3613
|
return raw.length > ARTIFACT_CAP_BYTES ? raw.slice(0, ARTIFACT_CAP_BYTES) : raw;
|
|
3345
3614
|
}
|
|
@@ -3367,14 +3636,14 @@ function readEmittedArtifact(ref, relPath) {
|
|
|
3367
3636
|
}
|
|
3368
3637
|
}
|
|
3369
3638
|
function scanScratchOutput(ref, preferRel) {
|
|
3639
|
+
const preferBase = preferRel?.split("/").pop();
|
|
3370
3640
|
try {
|
|
3371
|
-
const names = readdirSync3(
|
|
3641
|
+
const names = readdirSync3(join13(ref.worktreePath, SCRATCH_OUTPUT_DIR)).filter(
|
|
3372
3642
|
(n) => n.toLowerCase().endsWith(".md")
|
|
3373
3643
|
);
|
|
3374
3644
|
if (names.length === 0) return void 0;
|
|
3375
|
-
const preferBase = preferRel?.split("/").pop();
|
|
3376
3645
|
const pick = preferBase && names.includes(preferBase) ? preferBase : names[0];
|
|
3377
|
-
const raw = readFileSync5(
|
|
3646
|
+
const raw = readFileSync5(join13(ref.worktreePath, SCRATCH_OUTPUT_DIR, pick), "utf8");
|
|
3378
3647
|
if (raw.trim().length === 0) return void 0;
|
|
3379
3648
|
return { path: `${SCRATCH_OUTPUT_DIR}/${pick}`, content: capContent(raw) };
|
|
3380
3649
|
} catch {
|
|
@@ -3394,7 +3663,7 @@ function scanChangedMarkdown(ref) {
|
|
|
3394
3663
|
);
|
|
3395
3664
|
for (const rel of rels) {
|
|
3396
3665
|
try {
|
|
3397
|
-
const raw = readFileSync5(
|
|
3666
|
+
const raw = readFileSync5(join13(ref.worktreePath, rel), "utf8");
|
|
3398
3667
|
if (raw.trim().length > 0) return { path: rel, content: capContent(raw) };
|
|
3399
3668
|
} catch {
|
|
3400
3669
|
}
|
|
@@ -3415,6 +3684,9 @@ function resolveStageArtifact(ref, emitArtifact, handedBack) {
|
|
|
3415
3684
|
if (changed) return { artifact: changed, source: "changed-file" };
|
|
3416
3685
|
return void 0;
|
|
3417
3686
|
}
|
|
3687
|
+
function runtimeUsesMcpGateway(runtime) {
|
|
3688
|
+
return runtime === "claude-code" || runtime === "pi";
|
|
3689
|
+
}
|
|
3418
3690
|
function createStageRunner(deps) {
|
|
3419
3691
|
const worktrees = /* @__PURE__ */ new Map();
|
|
3420
3692
|
const log = deps.logger ?? createNodeLogger({ level: "silent" });
|
|
@@ -3437,7 +3709,7 @@ function createStageRunner(deps) {
|
|
|
3437
3709
|
if (ref) {
|
|
3438
3710
|
if (scratchOnly.has(runId)) {
|
|
3439
3711
|
try {
|
|
3440
|
-
|
|
3712
|
+
rmSync6(ref.worktreePath, { recursive: true, force: true });
|
|
3441
3713
|
} catch (e) {
|
|
3442
3714
|
log.warn({ err: e, runId, path: ref.worktreePath }, "teardown: could not remove scratch dir");
|
|
3443
3715
|
}
|
|
@@ -3517,9 +3789,9 @@ function createStageRunner(deps) {
|
|
|
3517
3789
|
...job.workspaceRef.credentialToken ? { credentialToken: job.workspaceRef.credentialToken } : {}
|
|
3518
3790
|
});
|
|
3519
3791
|
} else {
|
|
3520
|
-
const base = deps.scratchRoot ??
|
|
3521
|
-
const worktreePath =
|
|
3522
|
-
const scratchPath =
|
|
3792
|
+
const base = deps.scratchRoot ?? join13(tmpdir5(), "dahrk", "scratch");
|
|
3793
|
+
const worktreePath = join13(base, runId);
|
|
3794
|
+
const scratchPath = join13(worktreePath, ".dahrk", "scratch");
|
|
3523
3795
|
mkdirSync7(scratchPath, { recursive: true });
|
|
3524
3796
|
ref = { repoId: "", gitUrl: "", repo: "", baseBranch: "", worktreePath, scratchPath };
|
|
3525
3797
|
scratchOnly.add(runId);
|
|
@@ -3535,7 +3807,7 @@ function createStageRunner(deps) {
|
|
|
3535
3807
|
const slash = job.agentConfig.emitArtifact.lastIndexOf("/");
|
|
3536
3808
|
if (artifactDir && slash > 0) {
|
|
3537
3809
|
try {
|
|
3538
|
-
mkdirSync7(
|
|
3810
|
+
mkdirSync7(join13(ref.worktreePath, job.agentConfig.emitArtifact.slice(0, slash)), { recursive: true });
|
|
3539
3811
|
} catch {
|
|
3540
3812
|
}
|
|
3541
3813
|
}
|
|
@@ -3563,8 +3835,8 @@ function createStageRunner(deps) {
|
|
|
3563
3835
|
if (!sink) return;
|
|
3564
3836
|
const base = { tenantId: job.tenantId, runId, stageId, attempt };
|
|
3565
3837
|
try {
|
|
3566
|
-
for (const name of readdirSync3(
|
|
3567
|
-
const bytes = readFileSync5(
|
|
3838
|
+
for (const name of readdirSync3(join13(writer.dir, "blobs"))) {
|
|
3839
|
+
const bytes = readFileSync5(join13(writer.dir, "blobs", name));
|
|
3568
3840
|
const { url } = await sink.requestBlobUrl({
|
|
3569
3841
|
...base,
|
|
3570
3842
|
sha256: name,
|
|
@@ -3578,7 +3850,7 @@ function createStageRunner(deps) {
|
|
|
3578
3850
|
}
|
|
3579
3851
|
let archiveKey;
|
|
3580
3852
|
try {
|
|
3581
|
-
const bytes = readFileSync5(
|
|
3853
|
+
const bytes = readFileSync5(join13(writer.dir, "trace.jsonl"));
|
|
3582
3854
|
const sha = createHash3("sha256").update(bytes).digest("hex");
|
|
3583
3855
|
const { key, url } = await sink.requestBlobUrl({
|
|
3584
3856
|
...base,
|
|
@@ -3675,6 +3947,7 @@ function createStageRunner(deps) {
|
|
|
3675
3947
|
return finish("fail", `${stageId}: denied at stage entry (${entry.policy})`, job.sessionId);
|
|
3676
3948
|
}
|
|
3677
3949
|
let denied = false;
|
|
3950
|
+
const surfacedDenyReasons = /* @__PURE__ */ new Set();
|
|
3678
3951
|
let escapedUnblocked = false;
|
|
3679
3952
|
const authorisedActions = [];
|
|
3680
3953
|
const runtime = agentConfig.runtime;
|
|
@@ -3693,7 +3966,11 @@ function createStageRunner(deps) {
|
|
|
3693
3966
|
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "observation", runtime, toolUseId, isError: true, output: { error: reason } }));
|
|
3694
3967
|
}
|
|
3695
3968
|
streamEvent(writer.append({ seq: 0, ts: nowIso2(), type: "state", runtime, event: "policy-deny", detail: reason }));
|
|
3696
|
-
|
|
3969
|
+
const surfaceKey = `${verdict2.policy}\0${reason}`;
|
|
3970
|
+
if (!surfacedDenyReasons.has(surfaceKey)) {
|
|
3971
|
+
surfacedDenyReasons.add(surfaceKey);
|
|
3972
|
+
deps.sendProgress({ jobId, kind: "error", ts: nowIso2(), text: reason });
|
|
3973
|
+
}
|
|
3697
3974
|
};
|
|
3698
3975
|
const authorizeToolUse = (tool3, input) => {
|
|
3699
3976
|
const verdict2 = evaluatePolicies({ kind: "action", stageId, tool: tool3, input }, rules);
|
|
@@ -3704,7 +3981,10 @@ function createStageRunner(deps) {
|
|
|
3704
3981
|
}
|
|
3705
3982
|
return verdict2;
|
|
3706
3983
|
};
|
|
3984
|
+
let bumpStall = () => {
|
|
3985
|
+
};
|
|
3707
3986
|
const onTrace = (event) => {
|
|
3987
|
+
bumpStall();
|
|
3708
3988
|
if (event.type === "action") {
|
|
3709
3989
|
const key = actionKey(event.tool, event.input);
|
|
3710
3990
|
const authorised = authorisedActions.indexOf(key);
|
|
@@ -3727,7 +4007,7 @@ function createStageRunner(deps) {
|
|
|
3727
4007
|
if (event.type !== "state") deps.sendProgress({ jobId, kind: event.type, ts: event.ts, ...previewOf(event) });
|
|
3728
4008
|
};
|
|
3729
4009
|
const mcpServers = agentConfig.mcpServers;
|
|
3730
|
-
if (mcpServers && mcpServers.length > 0 && runtime
|
|
4010
|
+
if (mcpServers && mcpServers.length > 0 && runtimeUsesMcpGateway(runtime)) {
|
|
3731
4011
|
gateway = await startMcpGateway({ servers: mcpServers, creds: job.brokeredCreds ?? {} });
|
|
3732
4012
|
}
|
|
3733
4013
|
const runner = deps.makeRunner(runtime);
|
|
@@ -3769,6 +4049,20 @@ function createStageRunner(deps) {
|
|
|
3769
4049
|
timedOut = true;
|
|
3770
4050
|
void runner.cancel();
|
|
3771
4051
|
}, killMs) : void 0;
|
|
4052
|
+
let stalled = false;
|
|
4053
|
+
let stallTimer;
|
|
4054
|
+
const stallSource = agentConfig.stallMs ?? Number(process.env.DAHRK_BATCH_STALL_MS ?? process.env.SKAKEL_BATCH_STALL_MS ?? 3e5);
|
|
4055
|
+
const stallMs = interactive ? 0 : Math.max(0, Math.floor(stallSource));
|
|
4056
|
+
if (stallMs > 0) {
|
|
4057
|
+
bumpStall = () => {
|
|
4058
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
4059
|
+
stallTimer = setTimeout(() => {
|
|
4060
|
+
stalled = true;
|
|
4061
|
+
void runner.cancel();
|
|
4062
|
+
}, stallMs);
|
|
4063
|
+
};
|
|
4064
|
+
bumpStall();
|
|
4065
|
+
}
|
|
3772
4066
|
try {
|
|
3773
4067
|
if (interactive) {
|
|
3774
4068
|
const mailbox = new ManagedMailbox();
|
|
@@ -3779,8 +4073,9 @@ function createStageRunner(deps) {
|
|
|
3779
4073
|
}
|
|
3780
4074
|
} finally {
|
|
3781
4075
|
if (killTimer) clearTimeout(killTimer);
|
|
4076
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
3782
4077
|
}
|
|
3783
|
-
let status = timedOut ? "timeout" : result.status;
|
|
4078
|
+
let status = timedOut || stalled ? "timeout" : result.status;
|
|
3784
4079
|
if (status === "ok" && escapedUnblocked) {
|
|
3785
4080
|
status = "fail";
|
|
3786
4081
|
const msg = `stage reached outside the run's worktree and the ${runtime} runtime could not block it before it ran`;
|
|
@@ -3799,6 +4094,9 @@ function createStageRunner(deps) {
|
|
|
3799
4094
|
}
|
|
3800
4095
|
}
|
|
3801
4096
|
let summary = result.summary ?? `${stageId}: ${status}`;
|
|
4097
|
+
if (stalled && !timedOut) {
|
|
4098
|
+
summary = `${stageId}: stalled (no output for ${Math.round(stallMs / 1e3)}s)`;
|
|
4099
|
+
}
|
|
3802
4100
|
if (!interactive && status === "ok") {
|
|
3803
4101
|
try {
|
|
3804
4102
|
summary = await runner.summarise(ctx);
|
|
@@ -4040,6 +4338,17 @@ async function startEdgeNode(opts) {
|
|
|
4040
4338
|
if (oldest !== void 0) lastResults.delete(oldest);
|
|
4041
4339
|
}
|
|
4042
4340
|
};
|
|
4341
|
+
const ackedCancels = /* @__PURE__ */ new Map();
|
|
4342
|
+
const ackCancel = (jobId) => {
|
|
4343
|
+
const frame = { type: "cancel-ack", jobId };
|
|
4344
|
+
ackedCancels.delete(jobId);
|
|
4345
|
+
ackedCancels.set(jobId, frame);
|
|
4346
|
+
if (ackedCancels.size > MAX_RESEND) {
|
|
4347
|
+
const oldest = ackedCancels.keys().next().value;
|
|
4348
|
+
if (oldest !== void 0) ackedCancels.delete(oldest);
|
|
4349
|
+
}
|
|
4350
|
+
send(frame);
|
|
4351
|
+
};
|
|
4043
4352
|
const pendingBlob = /* @__PURE__ */ new Map();
|
|
4044
4353
|
let blobReqCounter = 0;
|
|
4045
4354
|
const trace = {
|
|
@@ -4220,6 +4529,7 @@ async function startEdgeNode(opts) {
|
|
|
4220
4529
|
if (msg.type === "cancel") {
|
|
4221
4530
|
log.info({ jobId: msg.jobId }, `JOB_CANCEL:${msg.jobId}`);
|
|
4222
4531
|
stageRunner.cancel(msg.jobId);
|
|
4532
|
+
ackCancel(msg.jobId);
|
|
4223
4533
|
return;
|
|
4224
4534
|
}
|
|
4225
4535
|
if (msg.type === "turn") {
|
|
@@ -4351,6 +4661,7 @@ async function startEdgeNode(opts) {
|
|
|
4351
4661
|
log.info({ hubUrl: opts.hubUrl, nodeId, connectCount }, "EDGE_CONNECTED");
|
|
4352
4662
|
sendHello();
|
|
4353
4663
|
for (const frame of lastResults.values()) send(frame);
|
|
4664
|
+
for (const frame of ackedCancels.values()) send(frame);
|
|
4354
4665
|
startHeartbeat(sock, opts.heartbeatMs ?? 5e3);
|
|
4355
4666
|
});
|
|
4356
4667
|
sock.on("message", (raw) => void onMessage(raw.toString()));
|
|
@@ -4454,7 +4765,6 @@ async function startEdgeNode(opts) {
|
|
|
4454
4765
|
import { execFile } from "child_process";
|
|
4455
4766
|
var PROBES = [
|
|
4456
4767
|
{ runtime: "claude-code", cmd: "claude" },
|
|
4457
|
-
{ runtime: "codex", cmd: "codex" },
|
|
4458
4768
|
{ runtime: "pi", cmd: "pi" }
|
|
4459
4769
|
];
|
|
4460
4770
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
@@ -5092,8 +5402,8 @@ function usage(bin, command) {
|
|
|
5092
5402
|
}
|
|
5093
5403
|
|
|
5094
5404
|
// src/diagnose.ts
|
|
5095
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as
|
|
5096
|
-
import { join as
|
|
5405
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
5406
|
+
import { join as join14 } from "path";
|
|
5097
5407
|
|
|
5098
5408
|
// src/ui.ts
|
|
5099
5409
|
import { styleText } from "util";
|
|
@@ -5208,7 +5518,7 @@ async function buildBundle(deps) {
|
|
|
5208
5518
|
if (deps.exists(deps.crashDir)) {
|
|
5209
5519
|
for (const name of deps.listDir(deps.crashDir).filter((f) => f.endsWith(".json")).sort()) {
|
|
5210
5520
|
try {
|
|
5211
|
-
crashes.push(JSON.parse(deps.readFile(
|
|
5521
|
+
crashes.push(JSON.parse(deps.readFile(join14(deps.crashDir, name))));
|
|
5212
5522
|
} catch (e) {
|
|
5213
5523
|
warnings.push(`could not read crash record ${name} (${e.message})`);
|
|
5214
5524
|
}
|
|
@@ -5269,14 +5579,14 @@ var defaultDiagnoseDeps = (paths, clientVersion, doctor) => ({
|
|
|
5269
5579
|
listDir: (p) => readdirSync4(p),
|
|
5270
5580
|
exists: (p) => existsSync8(p),
|
|
5271
5581
|
writeFile: (p, content) => {
|
|
5272
|
-
const dir =
|
|
5582
|
+
const dir = join14(p, "..");
|
|
5273
5583
|
if (!existsSync8(dir)) mkdirSync8(dir, { recursive: true });
|
|
5274
|
-
|
|
5584
|
+
writeFileSync8(p, content, { mode: 384 });
|
|
5275
5585
|
},
|
|
5276
5586
|
out
|
|
5277
5587
|
});
|
|
5278
5588
|
function defaultBundlePath(cwd, now) {
|
|
5279
|
-
return
|
|
5589
|
+
return join14(cwd, `dahrk-diagnose-${now.toISOString().replace(/[:.]/g, "-")}.json`);
|
|
5280
5590
|
}
|
|
5281
5591
|
|
|
5282
5592
|
// src/doctor.ts
|
|
@@ -5495,7 +5805,7 @@ async function registerRepo(deps, args) {
|
|
|
5495
5805
|
}
|
|
5496
5806
|
|
|
5497
5807
|
// src/lock.ts
|
|
5498
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync7, rmSync as
|
|
5808
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync7, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
5499
5809
|
import { dirname as dirname6 } from "path";
|
|
5500
5810
|
function parseLock(content) {
|
|
5501
5811
|
if (!content) return void 0;
|
|
@@ -5535,9 +5845,9 @@ var defaultLockDeps = (file) => ({
|
|
|
5535
5845
|
},
|
|
5536
5846
|
writeFile: (path, content) => {
|
|
5537
5847
|
if (!existsSync9(dirname6(path))) mkdirSync9(dirname6(path), { recursive: true, mode: 448 });
|
|
5538
|
-
|
|
5848
|
+
writeFileSync9(path, content);
|
|
5539
5849
|
},
|
|
5540
|
-
removeFile: (path) =>
|
|
5850
|
+
removeFile: (path) => rmSync7(path, { force: true }),
|
|
5541
5851
|
isAlive
|
|
5542
5852
|
});
|
|
5543
5853
|
|
|
@@ -5645,13 +5955,13 @@ var defaultLogsDeps = (files, jsonlFile) => ({
|
|
|
5645
5955
|
});
|
|
5646
5956
|
|
|
5647
5957
|
// src/process-safety.ts
|
|
5648
|
-
import { mkdirSync as mkdirSync10, writeFileSync as
|
|
5649
|
-
import { join as
|
|
5958
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
5959
|
+
import { join as join15 } from "path";
|
|
5650
5960
|
function writeCrashRecord(dir, record) {
|
|
5651
5961
|
try {
|
|
5652
5962
|
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
5653
|
-
const file =
|
|
5654
|
-
|
|
5963
|
+
const file = join15(dir, `${record.at.replace(/[:.]/g, "-")}.json`);
|
|
5964
|
+
writeFileSync10(file, `${JSON.stringify(record, null, 2)}
|
|
5655
5965
|
`, { mode: 384 });
|
|
5656
5966
|
return file;
|
|
5657
5967
|
} catch {
|
|
@@ -5708,7 +6018,7 @@ import { execFileSync as execFileSync6 } from "child_process";
|
|
|
5708
6018
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
5709
6019
|
import { accessSync, constants as fsConstants, existsSync as existsSync11, readdirSync as readdirSync5, statfsSync as statfsSync2 } from "fs";
|
|
5710
6020
|
import { homedir as homedir4 } from "os";
|
|
5711
|
-
import { join as
|
|
6021
|
+
import { join as join16 } from "path";
|
|
5712
6022
|
var REPORT_BASE_URL = "https://app.dahrk.ai/r";
|
|
5713
6023
|
var LOW_DISK_BYTES = 512 * 1024 * 1024;
|
|
5714
6024
|
var PREFLIGHT_STAGES = [
|
|
@@ -5847,7 +6157,7 @@ function commandPresent(cmd) {
|
|
|
5847
6157
|
}
|
|
5848
6158
|
function sshKeyPresent() {
|
|
5849
6159
|
try {
|
|
5850
|
-
const dir =
|
|
6160
|
+
const dir = join16(homedir4(), ".ssh");
|
|
5851
6161
|
if (existsSync11(dir) && readdirSync5(dir).some((f) => f.endsWith(".pub"))) return true;
|
|
5852
6162
|
} catch {
|
|
5853
6163
|
}
|
|
@@ -5867,12 +6177,12 @@ function writable(dir) {
|
|
|
5867
6177
|
}
|
|
5868
6178
|
}
|
|
5869
6179
|
function worktreeRoot(env) {
|
|
5870
|
-
return env.DAHRK_WORKTREES_DIR ??
|
|
6180
|
+
return env.DAHRK_WORKTREES_DIR ?? join16(env.DAHRK_STATE_DIR ?? join16(homedir4(), ".dahrk"), "worktrees");
|
|
5871
6181
|
}
|
|
5872
6182
|
function nearestExisting(dir) {
|
|
5873
6183
|
let cur = dir;
|
|
5874
6184
|
while (!existsSync11(cur)) {
|
|
5875
|
-
const parent =
|
|
6185
|
+
const parent = join16(cur, "..");
|
|
5876
6186
|
if (parent === cur) break;
|
|
5877
6187
|
cur = parent;
|
|
5878
6188
|
}
|
|
@@ -5934,14 +6244,14 @@ function gatherHostFacts(repoPath) {
|
|
|
5934
6244
|
|
|
5935
6245
|
// src/service.ts
|
|
5936
6246
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
5937
|
-
import { chmodSync as chmodSync3, existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync10, realpathSync as realpathSync3, rmSync as
|
|
6247
|
+
import { chmodSync as chmodSync3, existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync10, realpathSync as realpathSync3, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "fs";
|
|
5938
6248
|
import { homedir as homedir6, platform as osPlatform3, userInfo } from "os";
|
|
5939
|
-
import { join as
|
|
6249
|
+
import { join as join18 } from "path";
|
|
5940
6250
|
|
|
5941
6251
|
// src/state.ts
|
|
5942
|
-
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync9, writeFileSync as
|
|
6252
|
+
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
5943
6253
|
import { homedir as homedir5 } from "os";
|
|
5944
|
-
import { join as
|
|
6254
|
+
import { join as join17 } from "path";
|
|
5945
6255
|
var RUNTIMES = ["claude-code", "codex", "pi"];
|
|
5946
6256
|
var isRuntime = (v) => RUNTIMES.includes(v);
|
|
5947
6257
|
var STRING_FIELDS = ["nodeId", "enrolToken", "name", "tenantId", "updateCheckedAt", "updateLatest"];
|
|
@@ -5949,29 +6259,29 @@ var isDesired = (v) => v === "running" || v === "stopped";
|
|
|
5949
6259
|
var FILE_MODE2 = 384;
|
|
5950
6260
|
var DIR_MODE2 = 448;
|
|
5951
6261
|
function stateDir(env) {
|
|
5952
|
-
return env.DAHRK_STATE_DIR ??
|
|
6262
|
+
return env.DAHRK_STATE_DIR ?? join17(homedir5(), ".dahrk");
|
|
5953
6263
|
}
|
|
5954
6264
|
function legacyStateDir(env) {
|
|
5955
|
-
return env.DAHRK_STATE_DIR ? void 0 :
|
|
6265
|
+
return env.DAHRK_STATE_DIR ? void 0 : join17(homedir5(), ".skakel");
|
|
5956
6266
|
}
|
|
5957
6267
|
function stateFile(env) {
|
|
5958
|
-
return
|
|
6268
|
+
return join17(stateDir(env), "node.json");
|
|
5959
6269
|
}
|
|
5960
6270
|
function logDir(env) {
|
|
5961
|
-
return
|
|
6271
|
+
return join17(stateDir(env), "logs");
|
|
5962
6272
|
}
|
|
5963
6273
|
function logFiles(env) {
|
|
5964
6274
|
const dir = logDir(env);
|
|
5965
|
-
return { out:
|
|
6275
|
+
return { out: join17(dir, "node.out.log"), err: join17(dir, "node.err.log") };
|
|
5966
6276
|
}
|
|
5967
6277
|
function jsonlLogFile(env) {
|
|
5968
|
-
return
|
|
6278
|
+
return join17(logDir(env), "node.jsonl");
|
|
5969
6279
|
}
|
|
5970
6280
|
function crashDir(env) {
|
|
5971
|
-
return
|
|
6281
|
+
return join17(logDir(env), "crashes");
|
|
5972
6282
|
}
|
|
5973
6283
|
function lockFile(env) {
|
|
5974
|
-
return
|
|
6284
|
+
return join17(stateDir(env), "node.pid");
|
|
5975
6285
|
}
|
|
5976
6286
|
function setDesired(env, desired) {
|
|
5977
6287
|
writeState(env, { desired });
|
|
@@ -6001,7 +6311,7 @@ function writeState(env, patch) {
|
|
|
6001
6311
|
try {
|
|
6002
6312
|
mkdirSync11(dir, { recursive: true, mode: DIR_MODE2 });
|
|
6003
6313
|
const next = { ...readState(file), ...patch };
|
|
6004
|
-
|
|
6314
|
+
writeFileSync11(file, `${JSON.stringify(next, null, 2)}
|
|
6005
6315
|
`, { mode: FILE_MODE2 });
|
|
6006
6316
|
chmodSync2(file, FILE_MODE2);
|
|
6007
6317
|
} catch (e) {
|
|
@@ -6082,9 +6392,9 @@ ${envEntries}
|
|
|
6082
6392
|
<key>ThrottleInterval</key>
|
|
6083
6393
|
<integer>10</integer>
|
|
6084
6394
|
<key>StandardOutPath</key>
|
|
6085
|
-
<string>${xmlEscape(
|
|
6395
|
+
<string>${xmlEscape(join18(inputs.logDir, "node.out.log"))}</string>
|
|
6086
6396
|
<key>StandardErrorPath</key>
|
|
6087
|
-
<string>${xmlEscape(
|
|
6397
|
+
<string>${xmlEscape(join18(inputs.logDir, "node.err.log"))}</string>
|
|
6088
6398
|
</dict>
|
|
6089
6399
|
</plist>
|
|
6090
6400
|
`;
|
|
@@ -6107,8 +6417,8 @@ Type=simple
|
|
|
6107
6417
|
ExecStart=${exec}
|
|
6108
6418
|
${envLines}
|
|
6109
6419
|
WorkingDirectory=${inputs.homeDir}
|
|
6110
|
-
StandardOutput=append:${
|
|
6111
|
-
StandardError=append:${
|
|
6420
|
+
StandardOutput=append:${join18(inputs.logDir, "node.out.log")}
|
|
6421
|
+
StandardError=append:${join18(inputs.logDir, "node.err.log")}
|
|
6112
6422
|
Restart=on-failure
|
|
6113
6423
|
RestartSec=3
|
|
6114
6424
|
RestartPreventExitStatus=78
|
|
@@ -6119,7 +6429,7 @@ WantedBy=default.target
|
|
|
6119
6429
|
}
|
|
6120
6430
|
function buildPlan(inputs) {
|
|
6121
6431
|
if (inputs.manager === "launchd") {
|
|
6122
|
-
const filePath2 =
|
|
6432
|
+
const filePath2 = join18(inputs.homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
6123
6433
|
return {
|
|
6124
6434
|
manager: "launchd",
|
|
6125
6435
|
label: LAUNCHD_LABEL,
|
|
@@ -6136,7 +6446,7 @@ function buildPlan(inputs) {
|
|
|
6136
6446
|
logHint: "dahrk logs -f"
|
|
6137
6447
|
};
|
|
6138
6448
|
}
|
|
6139
|
-
const filePath =
|
|
6449
|
+
const filePath = join18(inputs.homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
6140
6450
|
const user = userInfo().username;
|
|
6141
6451
|
return {
|
|
6142
6452
|
manager: "systemd",
|
|
@@ -6166,7 +6476,7 @@ function unitIsCurrent(plan, onDisk) {
|
|
|
6166
6476
|
return onDisk === plan.content;
|
|
6167
6477
|
}
|
|
6168
6478
|
function unitPath(manager, homeDir) {
|
|
6169
|
-
return manager === "launchd" ?
|
|
6479
|
+
return manager === "launchd" ? join18(homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`) : join18(homeDir, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
6170
6480
|
}
|
|
6171
6481
|
function statusCommand(manager) {
|
|
6172
6482
|
return manager === "launchd" ? ["launchctl", "list", LAUNCHD_LABEL] : ["systemctl", "--user", "show", SYSTEMD_UNIT, "-p", "ActiveState", "-p", "MainPID"];
|
|
@@ -6468,7 +6778,7 @@ var defaultDeps3 = () => ({
|
|
|
6468
6778
|
// no reason for it to be world-readable. `writeFileSync`'s `mode` applies only when it CREATES the file,
|
|
6469
6779
|
// so chmod explicitly too - re-installing over a unit an older client left at 0644 must tighten it.
|
|
6470
6780
|
writeFile: (path, content) => {
|
|
6471
|
-
|
|
6781
|
+
writeFileSync12(path, content, { mode: UNIT_FILE_MODE });
|
|
6472
6782
|
chmodSync3(path, UNIT_FILE_MODE);
|
|
6473
6783
|
},
|
|
6474
6784
|
readFile: (path) => {
|
|
@@ -6478,7 +6788,7 @@ var defaultDeps3 = () => ({
|
|
|
6478
6788
|
return void 0;
|
|
6479
6789
|
}
|
|
6480
6790
|
},
|
|
6481
|
-
removeFile: (path) =>
|
|
6791
|
+
removeFile: (path) => rmSync8(path, { force: true }),
|
|
6482
6792
|
fileExists: (path) => existsSync13(path),
|
|
6483
6793
|
// Captured, never inherited. A supervisor's stderr is ours to relay when it matters, not to leak
|
|
6484
6794
|
// unconditionally: `runCommands` decides who needs to read it.
|
|
@@ -6919,7 +7229,7 @@ async function runStatus(inputs, deps) {
|
|
|
6919
7229
|
}
|
|
6920
7230
|
|
|
6921
7231
|
// src/main.ts
|
|
6922
|
-
var CLIENT_VERSION = "0.1.
|
|
7232
|
+
var CLIENT_VERSION = "0.1.21";
|
|
6923
7233
|
var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
|
|
6924
7234
|
var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
6925
7235
|
var RUNTIMES2 = ["claude-code", "codex", "pi"];
|
|
@@ -6931,7 +7241,7 @@ function resolveNodeId(env, opts = {}) {
|
|
|
6931
7241
|
if (existing) return existing;
|
|
6932
7242
|
const legacy = legacyStateDir(env);
|
|
6933
7243
|
if (legacy) {
|
|
6934
|
-
const legacyId = readState(
|
|
7244
|
+
const legacyId = readState(join19(legacy, "node.json")).nodeId;
|
|
6935
7245
|
if (legacyId) return legacyId;
|
|
6936
7246
|
}
|
|
6937
7247
|
const nodeId = randomUUID3();
|