dahrk-node 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +250 -16
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -2004,6 +2004,160 @@ function makeRunner(runtime) {
|
|
|
2004
2004
|
return runtime === "codex" ? createCodexRunner() : createClaudeRunner();
|
|
2005
2005
|
}
|
|
2006
2006
|
|
|
2007
|
+
// ../../packages/edge/src/health.ts
|
|
2008
|
+
import { statfsSync } from "fs";
|
|
2009
|
+
var HealthCounters = class {
|
|
2010
|
+
startedAt = Date.now();
|
|
2011
|
+
errors = /* @__PURE__ */ new Map();
|
|
2012
|
+
crashes = 0;
|
|
2013
|
+
/** Connections made this process lifetime. The flapping signal: this climbing while uptime ALSO climbs
|
|
2014
|
+
* means the socket keeps dropping under a process that is otherwise fine. */
|
|
2015
|
+
connectCount = 0;
|
|
2016
|
+
/** Jobs in flight. A node stuck at 1 for an hour is wedged, and nothing else reveals it. */
|
|
2017
|
+
activeJobs = 0;
|
|
2018
|
+
/** Worktrees on disk. Climbing without bound was the shape of the leak DHK-371 fixed. */
|
|
2019
|
+
worktreeCount = 0;
|
|
2020
|
+
/** Bucket a stage failure. The CLASS only - the message would carry their paths. */
|
|
2021
|
+
recordError(kind) {
|
|
2022
|
+
this.errors.set(kind, (this.errors.get(kind) ?? 0) + 1);
|
|
2023
|
+
}
|
|
2024
|
+
recordCrash() {
|
|
2025
|
+
this.crashes++;
|
|
2026
|
+
}
|
|
2027
|
+
uptimeSec() {
|
|
2028
|
+
return Math.round((Date.now() - this.startedAt) / 1e3);
|
|
2029
|
+
}
|
|
2030
|
+
errorCounts() {
|
|
2031
|
+
return Object.fromEntries(this.errors);
|
|
2032
|
+
}
|
|
2033
|
+
crashCount() {
|
|
2034
|
+
return this.crashes;
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
function diskFreeBytes(path) {
|
|
2038
|
+
try {
|
|
2039
|
+
const st = statfsSync(path);
|
|
2040
|
+
return Number(st.bfree) * Number(st.bsize);
|
|
2041
|
+
} catch {
|
|
2042
|
+
return void 0;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
function collectHealth(inputs) {
|
|
2046
|
+
const { counters } = inputs;
|
|
2047
|
+
const free = diskFreeBytes(inputs.worktreesDir);
|
|
2048
|
+
const errors = counters.errorCounts();
|
|
2049
|
+
return {
|
|
2050
|
+
uptimeSec: counters.uptimeSec(),
|
|
2051
|
+
clientVersion: inputs.clientVersion,
|
|
2052
|
+
activeJobs: counters.activeJobs,
|
|
2053
|
+
connectCount: counters.connectCount,
|
|
2054
|
+
worktreeCount: counters.worktreeCount,
|
|
2055
|
+
...free !== void 0 ? { diskFreeBytes: free } : {},
|
|
2056
|
+
runtimes: inputs.runtimes,
|
|
2057
|
+
...Object.keys(errors).length > 0 ? { errors } : {},
|
|
2058
|
+
...counters.crashCount() > 0 ? { crashes: counters.crashCount() } : {}
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
// ../../packages/edge/src/log-shipper.ts
|
|
2063
|
+
import { shouldShip } from "@dahrk/contracts";
|
|
2064
|
+
var SHIP_BUFFER_MAX = 500;
|
|
2065
|
+
var SHIP_BATCH_MAX = 200;
|
|
2066
|
+
var SHIP_FLUSH_MS = 2e3;
|
|
2067
|
+
var LogShipper = class {
|
|
2068
|
+
constructor(opts = {}) {
|
|
2069
|
+
this.opts = opts;
|
|
2070
|
+
this.ceiling = opts.ceiling ?? { health: true, logs: "debug" };
|
|
2071
|
+
this.policy = this.clampToCeiling(opts.initial ?? { health: true, logs: "off" });
|
|
2072
|
+
}
|
|
2073
|
+
opts;
|
|
2074
|
+
buffer = [];
|
|
2075
|
+
policy;
|
|
2076
|
+
ceiling;
|
|
2077
|
+
timer;
|
|
2078
|
+
/** Attached once the socket exists. The shipper is constructed FIRST, because it has to be a pino
|
|
2079
|
+
* stream before the logger is built, which is before the client is started. Until `attach`, records
|
|
2080
|
+
* simply accumulate in the (bounded) ring. */
|
|
2081
|
+
send;
|
|
2082
|
+
/** Records we threw away because the buffer was full. Reported, never hidden. */
|
|
2083
|
+
dropped = 0;
|
|
2084
|
+
/** Give the shipper its transport. Called by the client once the socket exists. */
|
|
2085
|
+
attach(send) {
|
|
2086
|
+
this.send = send;
|
|
2087
|
+
}
|
|
2088
|
+
/** The hub may only ever ask for LESS than the local ceiling allows. */
|
|
2089
|
+
clampToCeiling(p) {
|
|
2090
|
+
const rank = { off: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
2091
|
+
const logs = rank[p.logs] > rank[this.ceiling.logs] ? this.ceiling.logs : p.logs;
|
|
2092
|
+
return { health: p.health && this.ceiling.health, logs };
|
|
2093
|
+
}
|
|
2094
|
+
/** Apply a policy from the hub (`welcome`, or a live `policy` frame). */
|
|
2095
|
+
setPolicy(p) {
|
|
2096
|
+
this.policy = this.clampToCeiling(p);
|
|
2097
|
+
if (this.policy.logs === "off") this.buffer = [];
|
|
2098
|
+
}
|
|
2099
|
+
current() {
|
|
2100
|
+
return this.policy;
|
|
2101
|
+
}
|
|
2102
|
+
/** Offer a record. Cheap and synchronous - this sits on the logging hot path. */
|
|
2103
|
+
offer(record) {
|
|
2104
|
+
if (this.policy.logs === "off") return;
|
|
2105
|
+
if (!shouldShip(record.level, this.policy.logs)) return;
|
|
2106
|
+
this.buffer.push(record);
|
|
2107
|
+
if (this.buffer.length > (this.opts.bufferMax ?? SHIP_BUFFER_MAX)) {
|
|
2108
|
+
this.buffer.shift();
|
|
2109
|
+
this.dropped++;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
/** Send one batch if there is anything to send and the socket will take it. */
|
|
2113
|
+
flush() {
|
|
2114
|
+
if (this.buffer.length === 0 || !this.send) return;
|
|
2115
|
+
const batch = this.buffer.slice(0, SHIP_BATCH_MAX);
|
|
2116
|
+
if (!this.send(batch)) return;
|
|
2117
|
+
this.buffer = this.buffer.slice(batch.length);
|
|
2118
|
+
}
|
|
2119
|
+
start() {
|
|
2120
|
+
if (this.timer) return;
|
|
2121
|
+
this.timer = setInterval(() => this.flush(), this.opts.flushMs ?? SHIP_FLUSH_MS);
|
|
2122
|
+
this.timer.unref?.();
|
|
2123
|
+
}
|
|
2124
|
+
stop() {
|
|
2125
|
+
if (this.timer) clearInterval(this.timer);
|
|
2126
|
+
this.timer = void 0;
|
|
2127
|
+
}
|
|
2128
|
+
/** For the health report and for tests. */
|
|
2129
|
+
pending() {
|
|
2130
|
+
return this.buffer.length;
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
function shipperStream(shipper) {
|
|
2134
|
+
return {
|
|
2135
|
+
write(chunk) {
|
|
2136
|
+
try {
|
|
2137
|
+
const r = JSON.parse(chunk);
|
|
2138
|
+
if (!r.msg) return;
|
|
2139
|
+
shipper.offer({
|
|
2140
|
+
level: r.level,
|
|
2141
|
+
time: r.time,
|
|
2142
|
+
msg: r.msg,
|
|
2143
|
+
...r.runId ? { runId: r.runId } : {},
|
|
2144
|
+
...r.stageId ? { stageId: r.stageId } : {},
|
|
2145
|
+
...r.jobId ? { jobId: r.jobId } : {},
|
|
2146
|
+
...r.component ? { component: r.component } : {},
|
|
2147
|
+
...r.err ? { err: r.err } : {}
|
|
2148
|
+
});
|
|
2149
|
+
} catch {
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
function ceilingFromEnv(env) {
|
|
2155
|
+
const v = env.DAHRK_TELEMETRY?.toLowerCase();
|
|
2156
|
+
if (v === "off" || v === "0" || v === "false") return { health: false, logs: "off" };
|
|
2157
|
+
if (v === "health") return { health: true, logs: "off" };
|
|
2158
|
+
return { health: true, logs: "debug" };
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2007
2161
|
// ../../packages/edge/src/logger.ts
|
|
2008
2162
|
import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync5, openSync, renameSync, rmSync as rmSync4, statSync as statSync2, writeSync } from "fs";
|
|
2009
2163
|
import { join as join7 } from "path";
|
|
@@ -2203,7 +2357,8 @@ function createNodeLogger(opts = {}) {
|
|
|
2203
2357
|
`);
|
|
2204
2358
|
}
|
|
2205
2359
|
}
|
|
2206
|
-
|
|
2360
|
+
if (opts.ship) streams.push({ level: "trace", stream: opts.ship });
|
|
2361
|
+
const base = streams.length === 0 ? "silent" : opts.ship ? "trace" : opts.dir ? lowest(level, fileLevel) : level;
|
|
2207
2362
|
return pino(
|
|
2208
2363
|
{
|
|
2209
2364
|
level: base,
|
|
@@ -2219,12 +2374,13 @@ function createNodeLogger(opts = {}) {
|
|
|
2219
2374
|
pino.multistream(streams)
|
|
2220
2375
|
);
|
|
2221
2376
|
}
|
|
2222
|
-
function createNodeLoggerFromEnv(env, dir, base) {
|
|
2377
|
+
function createNodeLoggerFromEnv(env, dir, base, ship) {
|
|
2223
2378
|
const fileLevel = fileLevelFromEnv(env);
|
|
2224
2379
|
return createNodeLogger({
|
|
2225
2380
|
level: levelFromEnv(env),
|
|
2226
2381
|
...fileLevel === "off" ? {} : { dir, fileLevel },
|
|
2227
|
-
...base ? { base } : {}
|
|
2382
|
+
...base ? { base } : {},
|
|
2383
|
+
...ship ? { ship } : {}
|
|
2228
2384
|
});
|
|
2229
2385
|
}
|
|
2230
2386
|
|
|
@@ -2447,11 +2603,12 @@ async function startMcpGateway(opts) {
|
|
|
2447
2603
|
// ../../packages/edge/src/stage-runner.ts
|
|
2448
2604
|
var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2449
2605
|
var PREVIEW = 500;
|
|
2606
|
+
var RESULT = 16e3;
|
|
2450
2607
|
var putBytes = async (url, body, contentType) => {
|
|
2451
2608
|
await fetch(url, { method: "PUT", headers: { "content-type": contentType }, body: new Uint8Array(body) });
|
|
2452
2609
|
};
|
|
2453
2610
|
function previewOf(event) {
|
|
2454
|
-
const clip = (v) => (typeof v === "string" ? v : JSON.stringify(v) ?? "").slice(0,
|
|
2611
|
+
const clip = (v, max = PREVIEW) => (typeof v === "string" ? v : JSON.stringify(v) ?? "").slice(0, max);
|
|
2455
2612
|
switch (event.type) {
|
|
2456
2613
|
case "response":
|
|
2457
2614
|
return event.text !== void 0 ? { text: event.text } : {};
|
|
@@ -2460,9 +2617,16 @@ function previewOf(event) {
|
|
|
2460
2617
|
case "thought":
|
|
2461
2618
|
return event.text !== void 0 ? { text: clip(event.text) } : {};
|
|
2462
2619
|
case "action":
|
|
2463
|
-
return {
|
|
2620
|
+
return {
|
|
2621
|
+
tool: event.tool,
|
|
2622
|
+
toolUseId: event.toolUseId,
|
|
2623
|
+
...event.input !== void 0 ? { text: clip(event.input) } : {}
|
|
2624
|
+
};
|
|
2464
2625
|
case "observation":
|
|
2465
|
-
return
|
|
2626
|
+
return {
|
|
2627
|
+
toolUseId: event.toolUseId,
|
|
2628
|
+
...event.output !== void 0 ? { text: clip(event.output, RESULT) } : {}
|
|
2629
|
+
};
|
|
2466
2630
|
case "error":
|
|
2467
2631
|
return { text: clip(event.message) };
|
|
2468
2632
|
default:
|
|
@@ -3125,9 +3289,20 @@ function createStageRunner(deps) {
|
|
|
3125
3289
|
|
|
3126
3290
|
// ../../packages/edge/src/ws-client.ts
|
|
3127
3291
|
var ENROLMENT_REJECTED_EXIT_CODE = 78;
|
|
3292
|
+
function classifyError(e) {
|
|
3293
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3294
|
+
if (/\bgit\b|worktree|clone|fetch|branch|checkout|remote/i.test(msg)) return "git";
|
|
3295
|
+
if (/policy|denied|not permitted|forbidden/i.test(msg)) return "policy";
|
|
3296
|
+
if (/hub|websocket|socket|awakeable/i.test(msg)) return "hub";
|
|
3297
|
+
if (/runtime|claude|codex|\bpi\b|model|sdk|api key/i.test(msg)) return "runtime";
|
|
3298
|
+
return "internal";
|
|
3299
|
+
}
|
|
3128
3300
|
async function startEdgeNode(opts) {
|
|
3129
3301
|
const log = opts.logger ?? createNodeLogger({ level: levelFromEnv(process.env) });
|
|
3130
3302
|
const rules = opts.denyTool ? [denyToolRule(opts.denyTool)] : [];
|
|
3303
|
+
const counters = new HealthCounters();
|
|
3304
|
+
const shipper = opts.shipper;
|
|
3305
|
+
const telemetryCeiling = ceilingFromEnv(process.env);
|
|
3131
3306
|
const gitLog = log.child({ component: "git" });
|
|
3132
3307
|
const gitLogger = {
|
|
3133
3308
|
info: (msg) => gitLog.debug(msg),
|
|
@@ -3150,6 +3325,12 @@ async function startEdgeNode(opts) {
|
|
|
3150
3325
|
const send = (msg) => {
|
|
3151
3326
|
if (ws && ws.readyState === WebSocket.OPEN) ws.send(encode(msg));
|
|
3152
3327
|
};
|
|
3328
|
+
shipper?.attach((records) => {
|
|
3329
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
|
|
3330
|
+
ws.send(encode({ type: "node-log", records }));
|
|
3331
|
+
return true;
|
|
3332
|
+
});
|
|
3333
|
+
shipper?.start();
|
|
3153
3334
|
const MISSED_PONGS_BEFORE_DEAD = 3;
|
|
3154
3335
|
const startHeartbeat = (sock, intervalMs) => {
|
|
3155
3336
|
if (heartbeat) clearInterval(heartbeat);
|
|
@@ -3161,7 +3342,18 @@ async function startEdgeNode(opts) {
|
|
|
3161
3342
|
sock.terminate();
|
|
3162
3343
|
return;
|
|
3163
3344
|
}
|
|
3164
|
-
|
|
3345
|
+
const health = shipper?.current().health ?? telemetryCeiling.health;
|
|
3346
|
+
send(
|
|
3347
|
+
health ? {
|
|
3348
|
+
type: "heartbeat",
|
|
3349
|
+
health: collectHealth({
|
|
3350
|
+
counters,
|
|
3351
|
+
clientVersion: opts.clientVersion ?? "0.0.0",
|
|
3352
|
+
runtimes: opts.runtimes,
|
|
3353
|
+
worktreesDir: gitService.worktreesDir
|
|
3354
|
+
})
|
|
3355
|
+
} : { type: "heartbeat" }
|
|
3356
|
+
);
|
|
3165
3357
|
if (sock.readyState === WebSocket.OPEN) sock.ping();
|
|
3166
3358
|
}, intervalMs);
|
|
3167
3359
|
};
|
|
@@ -3206,6 +3398,7 @@ async function startEdgeNode(opts) {
|
|
|
3206
3398
|
const stageRunner = createStageRunner(stageDeps);
|
|
3207
3399
|
stageRunnerRef = stageRunner;
|
|
3208
3400
|
void stageRunner.reapWorktrees().then((r) => {
|
|
3401
|
+
counters.worktreeCount = Math.max(r.scanned - r.reaped.length, 0);
|
|
3209
3402
|
if (r.reaped.length) {
|
|
3210
3403
|
log.info(
|
|
3211
3404
|
{ reaped: r.reaped, scanned: r.scanned, skipped: r.skipped },
|
|
@@ -3224,8 +3417,15 @@ async function startEdgeNode(opts) {
|
|
|
3224
3417
|
if (opts.heartbeatMs === void 0 && msg.heartbeatMs > 0 && ws) {
|
|
3225
3418
|
startHeartbeat(ws, msg.heartbeatMs);
|
|
3226
3419
|
}
|
|
3420
|
+
if (msg.telemetry && shipper) shipper.setPolicy(msg.telemetry);
|
|
3227
3421
|
log.info(
|
|
3228
|
-
{
|
|
3422
|
+
{
|
|
3423
|
+
name: msg.name,
|
|
3424
|
+
tenantId: msg.tenantId,
|
|
3425
|
+
credentialMode: msg.credentialMode,
|
|
3426
|
+
heartbeatMs: msg.heartbeatMs,
|
|
3427
|
+
...shipper ? { telemetry: shipper.current() } : {}
|
|
3428
|
+
},
|
|
3229
3429
|
`EDGE_WELCOMED:${msg.name} tenant=${msg.tenantId} credentialMode=${msg.credentialMode}`
|
|
3230
3430
|
);
|
|
3231
3431
|
try {
|
|
@@ -3247,6 +3447,11 @@ async function startEdgeNode(opts) {
|
|
|
3247
3447
|
}
|
|
3248
3448
|
return;
|
|
3249
3449
|
}
|
|
3450
|
+
if (msg.type === "policy") {
|
|
3451
|
+
shipper?.setPolicy(msg.telemetry);
|
|
3452
|
+
log.info({ telemetry: shipper?.current() ?? msg.telemetry }, `EDGE_POLICY:logs=${shipper?.current().logs ?? msg.telemetry.logs}`);
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3250
3455
|
if (msg.type === "cancel") {
|
|
3251
3456
|
log.info({ jobId: msg.jobId }, `JOB_CANCEL:${msg.jobId}`);
|
|
3252
3457
|
stageRunner.cancel(msg.jobId);
|
|
@@ -3319,6 +3524,7 @@ async function startEdgeNode(opts) {
|
|
|
3319
3524
|
return;
|
|
3320
3525
|
}
|
|
3321
3526
|
running.add(job.jobId);
|
|
3527
|
+
counters.activeJobs = running.size;
|
|
3322
3528
|
const startedAt = Date.now();
|
|
3323
3529
|
jobLog.info({}, `JOB_STARTED:${job.stageId} ${job.jobId}`);
|
|
3324
3530
|
try {
|
|
@@ -3338,9 +3544,11 @@ async function startEdgeNode(opts) {
|
|
|
3338
3544
|
};
|
|
3339
3545
|
rememberResult(job.jobId, frame);
|
|
3340
3546
|
send(frame);
|
|
3547
|
+
counters.recordError(classifyError(e));
|
|
3341
3548
|
jobLog.error({ err: e, durationMs: Date.now() - startedAt }, `JOB_ERROR:${job.stageId} ${e.message}`);
|
|
3342
3549
|
} finally {
|
|
3343
3550
|
running.delete(job.jobId);
|
|
3551
|
+
counters.activeJobs = running.size;
|
|
3344
3552
|
}
|
|
3345
3553
|
};
|
|
3346
3554
|
const connect = () => {
|
|
@@ -3348,6 +3556,7 @@ async function startEdgeNode(opts) {
|
|
|
3348
3556
|
ws = sock;
|
|
3349
3557
|
sock.on("open", () => {
|
|
3350
3558
|
connectCount++;
|
|
3559
|
+
counters.connectCount = connectCount;
|
|
3351
3560
|
log.info({ hubUrl: opts.hubUrl, nodeId, connectCount }, "EDGE_CONNECTED");
|
|
3352
3561
|
send({
|
|
3353
3562
|
type: "hello",
|
|
@@ -4204,7 +4413,11 @@ function acquireLock(deps) {
|
|
|
4204
4413
|
}
|
|
4205
4414
|
deps.writeFile(deps.file, `${deps.pid}
|
|
4206
4415
|
`);
|
|
4207
|
-
return { ok: true, release: () =>
|
|
4416
|
+
return { ok: true, release: () => releaseLock(deps) };
|
|
4417
|
+
}
|
|
4418
|
+
function releaseLock(deps) {
|
|
4419
|
+
if (parseLock(deps.readFile(deps.file)) !== deps.pid) return;
|
|
4420
|
+
deps.removeFile(deps.file);
|
|
4208
4421
|
}
|
|
4209
4422
|
function isAlive(pid) {
|
|
4210
4423
|
try {
|
|
@@ -4398,7 +4611,7 @@ function installProcessSafetyNet(opts) {
|
|
|
4398
4611
|
// src/preflight.ts
|
|
4399
4612
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
4400
4613
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4401
|
-
import { accessSync, constants as fsConstants, existsSync as existsSync9, readdirSync as readdirSync5, statfsSync } from "fs";
|
|
4614
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync9, readdirSync as readdirSync5, statfsSync as statfsSync2 } from "fs";
|
|
4402
4615
|
import { homedir as homedir2 } from "os";
|
|
4403
4616
|
import { join as join11 } from "path";
|
|
4404
4617
|
var REPORT_BASE_URL = "https://app.dahrk.ai/r";
|
|
@@ -4570,7 +4783,7 @@ function nearestExisting(dir) {
|
|
|
4570
4783
|
}
|
|
4571
4784
|
function freeDiskBytes(dir) {
|
|
4572
4785
|
try {
|
|
4573
|
-
const s =
|
|
4786
|
+
const s = statfsSync2(nearestExisting(dir));
|
|
4574
4787
|
return s.bavail * s.bsize;
|
|
4575
4788
|
} catch {
|
|
4576
4789
|
return void 0;
|
|
@@ -4975,6 +5188,12 @@ async function runNodeStart(inputs, deps = {}) {
|
|
|
4975
5188
|
d.out(" stop: dahrk stop");
|
|
4976
5189
|
return { kind: "running", code: 0 };
|
|
4977
5190
|
}
|
|
5191
|
+
var STOP_FOREIGN_NODE = 3;
|
|
5192
|
+
function foreignNodePid(lock, servicePid, isAlive2) {
|
|
5193
|
+
const held = parseLock(lock);
|
|
5194
|
+
if (held === void 0 || held === servicePid) return void 0;
|
|
5195
|
+
return isAlive2(held) ? held : void 0;
|
|
5196
|
+
}
|
|
4978
5197
|
async function runNodeStop(deps = {}) {
|
|
4979
5198
|
const d = { ...defaultDeps3(), ...deps };
|
|
4980
5199
|
const manager = detectManager(d.platform);
|
|
@@ -4990,11 +5209,22 @@ async function runNodeStop(deps = {}) {
|
|
|
4990
5209
|
if (!d.fileExists(plan.filePath)) {
|
|
4991
5210
|
d.out("No service installed, so there is nothing to stop.");
|
|
4992
5211
|
d.out("If you are running a node in a terminal (`dahrk start --foreground`), stop it with Ctrl-C.");
|
|
4993
|
-
return 0;
|
|
5212
|
+
return reportForeignNode(d, void 0) ?? 0;
|
|
4994
5213
|
}
|
|
5214
|
+
const servicePid = parseServiceStatus(manager, true, d.capture(statusCommand(manager))).pid;
|
|
4995
5215
|
runCommands(plan.stopCommands, d);
|
|
4996
5216
|
d.out("Node stopped. It will stay stopped across reboots until you run `dahrk start`.");
|
|
4997
|
-
return 0;
|
|
5217
|
+
return reportForeignNode(d, servicePid) ?? 0;
|
|
5218
|
+
}
|
|
5219
|
+
function reportForeignNode(d, servicePid) {
|
|
5220
|
+
const pid = foreignNodePid(d.readFile(d.lockFile), servicePid, d.isAlive);
|
|
5221
|
+
if (pid === void 0) return void 0;
|
|
5222
|
+
d.out("");
|
|
5223
|
+
d.out(`WARNING: a node is STILL RUNNING on this host (pid ${pid}). \`dahrk stop\` cannot stop it.`);
|
|
5224
|
+
d.out("Something else is supervising it: pm2, a container, or `dahrk start --foreground` in a terminal.");
|
|
5225
|
+
d.out("It is not a stray copy - it holds this node's identity, so it is still connected to the hub and");
|
|
5226
|
+
d.out("still taking Jobs. Stop it where it was started (e.g. `pm2 delete dahrk-node`), or kill the pid.");
|
|
5227
|
+
return STOP_FOREIGN_NODE;
|
|
4998
5228
|
}
|
|
4999
5229
|
async function runServiceUninstall(deps = {}) {
|
|
5000
5230
|
const d = { ...defaultDeps3(), ...deps };
|
|
@@ -5058,6 +5288,8 @@ var defaultDeps3 = () => ({
|
|
|
5058
5288
|
nodeBin: stableNodeBin(process.execPath, realpathOrUndefined),
|
|
5059
5289
|
scriptPath: resolveScriptPath(),
|
|
5060
5290
|
logDir: logDir(process.env),
|
|
5291
|
+
lockFile: lockFile(process.env),
|
|
5292
|
+
isAlive,
|
|
5061
5293
|
// Snapshot the operator's PATH at install time so the daemon finds git + the runtime CLIs (Homebrew /
|
|
5062
5294
|
// npm-global bins) that a supervisor's minimal PATH would otherwise hide.
|
|
5063
5295
|
pathEnv: process.env.PATH,
|
|
@@ -5358,7 +5590,7 @@ async function runStatus(inputs, deps) {
|
|
|
5358
5590
|
}
|
|
5359
5591
|
|
|
5360
5592
|
// src/main.ts
|
|
5361
|
-
var CLIENT_VERSION = "0.1.
|
|
5593
|
+
var CLIENT_VERSION = "0.1.10";
|
|
5362
5594
|
var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
|
|
5363
5595
|
var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
5364
5596
|
var RUNTIMES = ["claude-code", "codex", "pi"];
|
|
@@ -5472,7 +5704,8 @@ async function startForeground(env, flags) {
|
|
|
5472
5704
|
rotateIfLarge(files.out);
|
|
5473
5705
|
rotateIfLarge(files.err);
|
|
5474
5706
|
const nodeId = resolveNodeId(env, { ephemeral: flags.ephemeral });
|
|
5475
|
-
const
|
|
5707
|
+
const shipper = new LogShipper({ ceiling: ceilingFromEnv(env) });
|
|
5708
|
+
const logger = createNodeLoggerFromEnv(env, logDir(env), { nodeId, clientVersion: CLIENT_VERSION }, shipperStream(shipper));
|
|
5476
5709
|
installProcessSafetyNet({
|
|
5477
5710
|
logger,
|
|
5478
5711
|
crashDir: crashDir(env),
|
|
@@ -5494,6 +5727,7 @@ async function startForeground(env, flags) {
|
|
|
5494
5727
|
await startEdgeNode({
|
|
5495
5728
|
...buildEdgeOptions(env, resolved),
|
|
5496
5729
|
logger,
|
|
5730
|
+
shipper,
|
|
5497
5731
|
...persist ? {
|
|
5498
5732
|
onEnrolled: (welcome) => persistEnrolment(env, { token, name: welcome.name, tenantId: welcome.tenantId })
|
|
5499
5733
|
} : {}
|
|
@@ -5502,7 +5736,7 @@ async function startForeground(env, flags) {
|
|
|
5502
5736
|
}
|
|
5503
5737
|
async function stop(env) {
|
|
5504
5738
|
const code = await runNodeStop();
|
|
5505
|
-
if (code === 0) setDesired(env, "stopped");
|
|
5739
|
+
if (code === 0 || code === STOP_FOREIGN_NODE) setDesired(env, "stopped");
|
|
5506
5740
|
return code;
|
|
5507
5741
|
}
|
|
5508
5742
|
function updateCheckDeps(env) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dahrk-node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"exports": "./dist/main.js",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@anthropic-ai/claude-agent-sdk": "0.3.183",
|
|
37
|
-
"@dahrk/contracts": "^0.
|
|
37
|
+
"@dahrk/contracts": "^0.2.0",
|
|
38
38
|
"@openai/codex-sdk": "0.141.0",
|
|
39
39
|
"pino": "^10.3.1",
|
|
40
40
|
"ws": "^8.18.0",
|