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.
Files changed (2) hide show
  1. package/dist/main.js +250 -16
  2. 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
- const base = streams.length === 0 ? "silent" : opts.dir ? lowest(level, fileLevel) : level;
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, PREVIEW);
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 { tool: event.tool, ...event.input !== void 0 ? { text: clip(event.input) } : {} };
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 event.output !== void 0 ? { text: clip(event.output) } : {};
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
- send({ type: "heartbeat" });
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
- { name: msg.name, tenantId: msg.tenantId, credentialMode: msg.credentialMode, heartbeatMs: msg.heartbeatMs },
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: () => deps.removeFile(deps.file) };
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 = statfsSync(nearestExisting(dir));
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.9";
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 logger = createNodeLoggerFromEnv(env, logDir(env), { nodeId, clientVersion: CLIENT_VERSION });
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.9",
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.1.2",
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",