@darkhunt-security/endpoint-codex 0.9.17 → 0.9.18

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "darkhunt-guard",
3
- "version": "0.9.17",
3
+ "version": "0.9.18",
4
4
  "hooks": "./hooks/hooks.json",
5
5
  "description": "Darkhunt endpoint plugin for Codex CLI \u2014 full session trace capture and PreToolUse guardrails.",
6
6
  "author": {
@@ -34,5 +34,10 @@ What the fields mean, and what to say:
34
34
  export is being rejected — check `last pass` to tell those apart.
35
35
  - **last pass FAILED** — read out the cause. Checkpoints are held on failure, so nothing
36
36
  has been lost; it will re-ship once the cause is fixed.
37
+ - **held N transcript(s) waiting on the platform** — a pass could not reach Darkhunt, so
38
+ those sessions are queued on this machine with their checkpoints held. The forwarder
39
+ retries on its own for about an hour after a failure, and every later hook retries
40
+ them too, so this clears itself once the platform answers. Say since when they have
41
+ been held; nothing is lost while it is non-zero.
37
42
 
38
43
  Do not run anything else, and do not offer to change the configuration unless asked.
@@ -18274,6 +18274,9 @@ function liveTranscripts(beat, now = Date.now()) {
18274
18274
  return [...paths];
18275
18275
  }
18276
18276
 
18277
+ // packages/core/dist/emit/emitter.js
18278
+ import { createHash as createHash2 } from "node:crypto";
18279
+
18277
18280
  // packages/core/dist/emit/ids.js
18278
18281
  import { createHash, randomBytes } from "node:crypto";
18279
18282
  var SPAN_ID_HEX = 16;
@@ -18313,6 +18316,20 @@ var SeededIdGenerator = class {
18313
18316
  };
18314
18317
 
18315
18318
  // packages/core/dist/emit/emitter.js
18319
+ var PAYLOAD_LIMIT = 128 * 1024;
18320
+ function capPayload(value, limit = PAYLOAD_LIMIT) {
18321
+ if (value === void 0 || value === null)
18322
+ return value;
18323
+ const text = typeof value === "string" ? value : JSON.stringify(value);
18324
+ if (text === void 0 || text.length <= limit)
18325
+ return value;
18326
+ const digest = createHash2("sha256").update(text).digest("hex").slice(0, 16);
18327
+ const marker = `\u2026 [truncated by darkhunt: ${text.length} chars total, sha256:${digest}]`;
18328
+ return `${text.slice(0, Math.max(0, limit - marker.length))}${marker}`;
18329
+ }
18330
+ function capText(text, limit = PAYLOAD_LIMIT) {
18331
+ return capPayload(text, limit);
18332
+ }
18316
18333
  var TOOL_SUMMARY_LIMIT = 2e3;
18317
18334
  function summarize(output) {
18318
18335
  const text = typeof output === "string" ? output : JSON.stringify(output ?? null);
@@ -18439,7 +18456,7 @@ var SessionEmitter = class {
18439
18456
  // A subagent's first record is the task it was given, and it has already been
18440
18457
  // absorbed by the time this fires — so the child trace opens with its brief
18441
18458
  // rather than empty.
18442
- ...this.lastUserText ? { input: this.lastUserText } : {},
18459
+ ...this.lastUserText ? { input: capText(this.lastUserText) } : {},
18443
18460
  ...this.options.userId !== void 0 ? { userId: this.options.userId } : {}
18444
18461
  }));
18445
18462
  this.sidechains.push(owner);
@@ -18480,7 +18497,7 @@ var SessionEmitter = class {
18480
18497
  case "thinking": {
18481
18498
  this.openGeneration(record, owner);
18482
18499
  const host = this.generation ?? owner;
18483
- this.seeded(["thinking", record.uuid], () => host.span("thinking", { startTime: record.ts, output: record.text })).end({ endTime: record.ts });
18500
+ this.seeded(["thinking", record.uuid], () => host.span("thinking", { startTime: record.ts, output: capPayload(record.text) })).end({ endTime: record.ts });
18484
18501
  break;
18485
18502
  }
18486
18503
  case "tool_call": {
@@ -18491,8 +18508,8 @@ var SessionEmitter = class {
18491
18508
  startTime: record.ts,
18492
18509
  ...record.toolName !== void 0 ? { toolName: record.toolName } : {},
18493
18510
  ...record.toolCallId !== void 0 ? { toolCallId: record.toolCallId } : {},
18494
- toolArguments: record.input,
18495
- input: record.input
18511
+ toolArguments: capPayload(record.input),
18512
+ input: capPayload(record.input)
18496
18513
  }));
18497
18514
  if (record.toolCallId)
18498
18515
  this.toolSpans.set(record.toolCallId, span);
@@ -18504,7 +18521,7 @@ var SessionEmitter = class {
18504
18521
  const span = record.toolCallId ? this.toolSpans.get(record.toolCallId) : void 0;
18505
18522
  if (span) {
18506
18523
  span.end({
18507
- output: record.output,
18524
+ output: capPayload(record.output),
18508
18525
  endTime: record.ts,
18509
18526
  ...record.isError ? { level: "ERROR" } : {},
18510
18527
  ...record.errorKind !== void 0 ? { statusMessage: record.errorKind } : {}
@@ -18518,7 +18535,7 @@ var SessionEmitter = class {
18518
18535
  this.endGeneration();
18519
18536
  this.seeded(["compaction", record.uuid], () => owner.span("compaction", {
18520
18537
  startTime: record.ts,
18521
- ...record.text ? { output: record.text } : {}
18538
+ ...record.text ? { output: capPayload(record.text) } : {}
18522
18539
  })).end({ endTime: record.ts });
18523
18540
  break;
18524
18541
  case "permission_grant": {
@@ -18580,7 +18597,7 @@ var SessionEmitter = class {
18580
18597
  ...this.title !== void 0 ? { name: this.title } : {},
18581
18598
  // The pass's result. Paired with the `input` set when the trace opened, this is
18582
18599
  // what stops the consumer inferring the trace's I/O from a descendant.
18583
- ...this.lastAssistantText !== void 0 ? { output: this.lastAssistantText } : {}
18600
+ ...this.lastAssistantText !== void 0 ? { output: capText(this.lastAssistantText) } : {}
18584
18601
  });
18585
18602
  this.trace.end();
18586
18603
  this.trace = void 0;
@@ -18663,7 +18680,7 @@ var SessionEmitter = class {
18663
18680
  // What the agent was asked. `TraceArgs.input` is construction-only — the SDK's
18664
18681
  // `update()` carries output alone — which is why `ingest` absorbs the user turn
18665
18682
  // before it calls this.
18666
- ...this.lastUserText ? { input: this.lastUserText } : {},
18683
+ ...this.lastUserText ? { input: capText(this.lastUserText) } : {},
18667
18684
  tags: [this.options.vendor, ...this.options.agentId ? ["subagent"] : []],
18668
18685
  metadata: {
18669
18686
  ...this.meta,
@@ -18732,7 +18749,7 @@ var SessionEmitter = class {
18732
18749
  buildInputMessages() {
18733
18750
  const messages = [];
18734
18751
  if (this.lastUserText !== void 0) {
18735
- messages.push({ role: "user", content: this.lastUserText });
18752
+ messages.push({ role: "user", content: capText(this.lastUserText) });
18736
18753
  }
18737
18754
  for (const result2 of this.pendingToolResults) {
18738
18755
  messages.push({ role: "tool", content: result2 });
@@ -18742,7 +18759,7 @@ var SessionEmitter = class {
18742
18759
  endGeneration() {
18743
18760
  if (!this.generation)
18744
18761
  return;
18745
- const output = this.outputText.join("\n");
18762
+ const output = capText(this.outputText.join("\n"));
18746
18763
  if (output)
18747
18764
  this.lastAssistantText = output;
18748
18765
  this.generation.end({
@@ -18784,6 +18801,25 @@ function loadCheckpoints(scope) {
18784
18801
  return {};
18785
18802
  }
18786
18803
  }
18804
+ function laggingTranscripts(checkpoints, now = statSize) {
18805
+ const paths = [];
18806
+ for (const [path, checkpoint] of Object.entries(checkpoints)) {
18807
+ if (path === SPOOL_KEY)
18808
+ continue;
18809
+ const size = now(path);
18810
+ if (size === void 0 || size === checkpoint.offset)
18811
+ continue;
18812
+ paths.push(path);
18813
+ }
18814
+ return paths;
18815
+ }
18816
+ function statSize(path) {
18817
+ try {
18818
+ return statSync2(path).size;
18819
+ } catch {
18820
+ return void 0;
18821
+ }
18822
+ }
18787
18823
  function orphanedScopes(vendor, scope) {
18788
18824
  const suffix = ".checkpoints.json";
18789
18825
  try {
@@ -18887,6 +18923,7 @@ function readNewLines(path, offset, maxBytes = DEFAULT_MAX_BYTES) {
18887
18923
  import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
18888
18924
  import { join as join7 } from "node:path";
18889
18925
  var STALE_MS = 5 * 60 * 1e3;
18926
+ var HEARTBEAT_MS = 60 * 1e3;
18890
18927
  function acquireLock(scope) {
18891
18928
  mkdirSync3(CONFIG_DIR, { recursive: true, mode: 448 });
18892
18929
  const path = join7(CONFIG_DIR, `${scope}.forwarder.lock`);
@@ -18908,7 +18945,17 @@ function acquireLock(scope) {
18908
18945
  } catch {
18909
18946
  return null;
18910
18947
  }
18911
- return () => rmSync(path, { force: true });
18948
+ const beat = setInterval(() => {
18949
+ try {
18950
+ writeFileSync3(path, JSON.stringify({ pid: process.pid, ts: Date.now() }), { mode: 384 });
18951
+ } catch {
18952
+ }
18953
+ }, HEARTBEAT_MS);
18954
+ beat.unref();
18955
+ return () => {
18956
+ clearInterval(beat);
18957
+ rmSync(path, { force: true });
18958
+ };
18912
18959
  }
18913
18960
  function isAlive(pid, kill = (p, s) => {
18914
18961
  process.kill(p, s);
@@ -19009,6 +19056,61 @@ function saveHealth(scope, health) {
19009
19056
  }
19010
19057
  }
19011
19058
 
19059
+ // packages/core/dist/forwarder/pending.js
19060
+ import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
19061
+ import { join as join9 } from "node:path";
19062
+ var MAX_PENDING_PATHS = 256;
19063
+ function pendingPath(scope) {
19064
+ return join9(CONFIG_DIR, `${scope}.pending.json`);
19065
+ }
19066
+ function loadPending(scope) {
19067
+ try {
19068
+ const backlog = JSON.parse(readFileSync6(pendingPath(scope), "utf8"));
19069
+ if (!Array.isArray(backlog.paths))
19070
+ return void 0;
19071
+ return backlog;
19072
+ } catch {
19073
+ return void 0;
19074
+ }
19075
+ }
19076
+ function pendingTranscripts(scope) {
19077
+ const backlog = loadPending(scope);
19078
+ if (!backlog)
19079
+ return [];
19080
+ return backlog.paths.filter((path) => {
19081
+ try {
19082
+ return existsSync3(path);
19083
+ } catch {
19084
+ return false;
19085
+ }
19086
+ });
19087
+ }
19088
+ function savePending(scope, paths, now = /* @__PURE__ */ new Date()) {
19089
+ try {
19090
+ const at = now.toISOString();
19091
+ const previous = loadPending(scope);
19092
+ const merged = [.../* @__PURE__ */ new Set([...previous?.paths ?? [], ...paths])];
19093
+ const kept = merged.slice(Math.max(0, merged.length - MAX_PENDING_PATHS));
19094
+ if (kept.length === 0)
19095
+ return;
19096
+ write(scope, { since: previous?.since ?? at, at, paths: kept });
19097
+ } catch {
19098
+ }
19099
+ }
19100
+ function clearPending(scope) {
19101
+ try {
19102
+ rmSync2(pendingPath(scope), { force: true });
19103
+ } catch {
19104
+ }
19105
+ }
19106
+ function write(scope, backlog) {
19107
+ mkdirSync5(CONFIG_DIR, { recursive: true, mode: 448 });
19108
+ const target = pendingPath(scope);
19109
+ const tmp = `${target}.tmp`;
19110
+ writeFileSync5(tmp, JSON.stringify(backlog), { mode: 384 });
19111
+ renameSync4(tmp, target);
19112
+ }
19113
+
19012
19114
  // packages/core/dist/forwarder/run.js
19013
19115
  import { readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
19014
19116
 
@@ -21844,7 +21946,14 @@ async function runForwarder(mapper, options = {}) {
21844
21946
  }
21845
21947
  }
21846
21948
  const drain = options.paths ? { paths: options.paths } : drainSpool(mapper, checkpoints);
21847
- const named = options.paths ? drain.paths : [.../* @__PURE__ */ new Set([...drain.paths, ...liveTranscripts(loadBeat(config.scope))])];
21949
+ const named = options.paths ? drain.paths : [
21950
+ .../* @__PURE__ */ new Set([
21951
+ ...drain.paths,
21952
+ ...liveTranscripts(loadBeat(config.scope)),
21953
+ ...pendingTranscripts(config.scope),
21954
+ ...laggingTranscripts(checkpoints)
21955
+ ])
21956
+ ];
21848
21957
  const paths = [...new Set(named.flatMap((path) => [path, ...related(mapper, path)]))];
21849
21958
  if (paths.length === 0) {
21850
21959
  if (drain.checkpoint)
@@ -21941,13 +22050,16 @@ async function runForwarder(mapper, options = {}) {
21941
22050
  result2.emitted = { transcripts: result2.transcripts, records: result2.records };
21942
22051
  if (watch.failures() > 0) {
21943
22052
  result2.ok = false;
22053
+ result2.retryable = true;
21944
22054
  result2.error = watch.reason() ?? "span export failed";
21945
22055
  result2.transcripts = 0;
21946
22056
  result2.records = 0;
22057
+ savePending(config.scope, paths);
21947
22058
  } else {
21948
22059
  if (drain.checkpoint)
21949
22060
  staged[SPOOL_KEY] = drain.checkpoint;
21950
22061
  saveCheckpoints(config.scope, staged);
22062
+ clearPending(config.scope);
21951
22063
  }
21952
22064
  const previous = loadHealth(config.scope);
21953
22065
  saveHealth(config.scope, {
@@ -22012,13 +22124,16 @@ function discoverTranscripts(mapper) {
22012
22124
  return found;
22013
22125
  }
22014
22126
 
22127
+ // packages/core/dist/forwarder/retry.js
22128
+ var CLAIM_STALE_MS = 45 * 60 * 1e3;
22129
+
22015
22130
  // packages/core/dist/cli/status.js
22016
22131
  var SESSION_HOOK_STALE_MS = 30 * 60 * 1e3;
22017
22132
 
22018
22133
  // packages/core/dist/cli/enroll.js
22019
22134
  import { homedir as homedir3 } from "node:os";
22020
- import { join as join9 } from "node:path";
22021
- var CLI_CREDENTIALS_PATH = join9(homedir3(), ".darkhunt", "credentials.json");
22135
+ import { join as join10 } from "node:path";
22136
+ var CLI_CREDENTIALS_PATH = join10(homedir3(), ".darkhunt", "credentials.json");
22022
22137
 
22023
22138
  // packages/core/dist/cli/backfill.js
22024
22139
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Map([
@@ -22251,8 +22366,8 @@ async function runPasses(groups, pass, say2) {
22251
22366
  }
22252
22367
 
22253
22368
  // adapters/codex/dist/transcript.js
22254
- import { readFileSync as readFileSync6 } from "node:fs";
22255
- import { basename, join as join10 } from "node:path";
22369
+ import { readFileSync as readFileSync7 } from "node:fs";
22370
+ import { basename, join as join11 } from "node:path";
22256
22371
  import { homedir as homedir4 } from "node:os";
22257
22372
  function str(value) {
22258
22373
  return typeof value === "string" ? value : void 0;
@@ -22396,7 +22511,7 @@ function stripSuffix(path) {
22396
22511
  var codexTranscript = {
22397
22512
  vendor: "codex",
22398
22513
  sessionRoots() {
22399
- return [join10(homedir4(), ".codex", "sessions")];
22514
+ return [join11(homedir4(), ".codex", "sessions")];
22400
22515
  },
22401
22516
  /**
22402
22517
  * `~/.codex/auth.json` -> the `email` claim of `tokens.id_token`.
@@ -22418,7 +22533,7 @@ var codexTranscript = {
22418
22533
  */
22419
22534
  resolveUserId() {
22420
22535
  try {
22421
- return emailFromAuth(readFileSync6(join10(homedir4(), ".codex", "auth.json"), "utf8"));
22536
+ return emailFromAuth(readFileSync7(join11(homedir4(), ".codex", "auth.json"), "utf8"));
22422
22537
  } catch {
22423
22538
  return void 0;
22424
22539
  }
@@ -60,12 +60,16 @@ async function readStdin(stream) {
60
60
  return Buffer.concat(chunks).toString("utf8");
61
61
  }
62
62
 
63
+ // packages/core/dist/emit/emitter.js
64
+ var PAYLOAD_LIMIT = 128 * 1024;
65
+
63
66
  // packages/core/dist/forwarder/tail.js
64
67
  var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
65
68
  var LINE_CEILING = 64 * 1024 * 1024;
66
69
 
67
70
  // packages/core/dist/forwarder/lock.js
68
71
  var STALE_MS = 5 * 60 * 1e3;
72
+ var HEARTBEAT_MS = 60 * 1e3;
69
73
  var DEFAULT_LOCK_WAIT_MS = 60 * 1e3;
70
74
 
71
75
  // node_modules/@darkhunt-security/telemetry/package.json
@@ -994,6 +998,9 @@ var VALIDATORS = Object.freeze({
994
998
  // node_modules/@darkhunt-security/telemetry/dist/client.js
995
999
  var LIB_VERSION = package_default.version;
996
1000
 
1001
+ // packages/core/dist/forwarder/retry.js
1002
+ var CLAIM_STALE_MS = 45 * 60 * 1e3;
1003
+
997
1004
  // packages/core/dist/cli/init.js
998
1005
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
999
1006
  function init(argv) {
@@ -18260,6 +18260,9 @@ function liveTranscripts(beat, now = Date.now()) {
18260
18260
  return [...paths];
18261
18261
  }
18262
18262
 
18263
+ // packages/core/dist/emit/emitter.js
18264
+ import { createHash as createHash2 } from "node:crypto";
18265
+
18263
18266
  // packages/core/dist/emit/ids.js
18264
18267
  import { createHash, randomBytes } from "node:crypto";
18265
18268
  var SPAN_ID_HEX = 16;
@@ -18299,6 +18302,20 @@ var SeededIdGenerator = class {
18299
18302
  };
18300
18303
 
18301
18304
  // packages/core/dist/emit/emitter.js
18305
+ var PAYLOAD_LIMIT = 128 * 1024;
18306
+ function capPayload(value, limit = PAYLOAD_LIMIT) {
18307
+ if (value === void 0 || value === null)
18308
+ return value;
18309
+ const text = typeof value === "string" ? value : JSON.stringify(value);
18310
+ if (text === void 0 || text.length <= limit)
18311
+ return value;
18312
+ const digest = createHash2("sha256").update(text).digest("hex").slice(0, 16);
18313
+ const marker = `\u2026 [truncated by darkhunt: ${text.length} chars total, sha256:${digest}]`;
18314
+ return `${text.slice(0, Math.max(0, limit - marker.length))}${marker}`;
18315
+ }
18316
+ function capText(text, limit = PAYLOAD_LIMIT) {
18317
+ return capPayload(text, limit);
18318
+ }
18302
18319
  var TOOL_SUMMARY_LIMIT = 2e3;
18303
18320
  function summarize(output) {
18304
18321
  const text = typeof output === "string" ? output : JSON.stringify(output ?? null);
@@ -18425,7 +18442,7 @@ var SessionEmitter = class {
18425
18442
  // A subagent's first record is the task it was given, and it has already been
18426
18443
  // absorbed by the time this fires — so the child trace opens with its brief
18427
18444
  // rather than empty.
18428
- ...this.lastUserText ? { input: this.lastUserText } : {},
18445
+ ...this.lastUserText ? { input: capText(this.lastUserText) } : {},
18429
18446
  ...this.options.userId !== void 0 ? { userId: this.options.userId } : {}
18430
18447
  }));
18431
18448
  this.sidechains.push(owner);
@@ -18466,7 +18483,7 @@ var SessionEmitter = class {
18466
18483
  case "thinking": {
18467
18484
  this.openGeneration(record, owner);
18468
18485
  const host = this.generation ?? owner;
18469
- this.seeded(["thinking", record.uuid], () => host.span("thinking", { startTime: record.ts, output: record.text })).end({ endTime: record.ts });
18486
+ this.seeded(["thinking", record.uuid], () => host.span("thinking", { startTime: record.ts, output: capPayload(record.text) })).end({ endTime: record.ts });
18470
18487
  break;
18471
18488
  }
18472
18489
  case "tool_call": {
@@ -18477,8 +18494,8 @@ var SessionEmitter = class {
18477
18494
  startTime: record.ts,
18478
18495
  ...record.toolName !== void 0 ? { toolName: record.toolName } : {},
18479
18496
  ...record.toolCallId !== void 0 ? { toolCallId: record.toolCallId } : {},
18480
- toolArguments: record.input,
18481
- input: record.input
18497
+ toolArguments: capPayload(record.input),
18498
+ input: capPayload(record.input)
18482
18499
  }));
18483
18500
  if (record.toolCallId)
18484
18501
  this.toolSpans.set(record.toolCallId, span);
@@ -18490,7 +18507,7 @@ var SessionEmitter = class {
18490
18507
  const span = record.toolCallId ? this.toolSpans.get(record.toolCallId) : void 0;
18491
18508
  if (span) {
18492
18509
  span.end({
18493
- output: record.output,
18510
+ output: capPayload(record.output),
18494
18511
  endTime: record.ts,
18495
18512
  ...record.isError ? { level: "ERROR" } : {},
18496
18513
  ...record.errorKind !== void 0 ? { statusMessage: record.errorKind } : {}
@@ -18504,7 +18521,7 @@ var SessionEmitter = class {
18504
18521
  this.endGeneration();
18505
18522
  this.seeded(["compaction", record.uuid], () => owner.span("compaction", {
18506
18523
  startTime: record.ts,
18507
- ...record.text ? { output: record.text } : {}
18524
+ ...record.text ? { output: capPayload(record.text) } : {}
18508
18525
  })).end({ endTime: record.ts });
18509
18526
  break;
18510
18527
  case "permission_grant": {
@@ -18566,7 +18583,7 @@ var SessionEmitter = class {
18566
18583
  ...this.title !== void 0 ? { name: this.title } : {},
18567
18584
  // The pass's result. Paired with the `input` set when the trace opened, this is
18568
18585
  // what stops the consumer inferring the trace's I/O from a descendant.
18569
- ...this.lastAssistantText !== void 0 ? { output: this.lastAssistantText } : {}
18586
+ ...this.lastAssistantText !== void 0 ? { output: capText(this.lastAssistantText) } : {}
18570
18587
  });
18571
18588
  this.trace.end();
18572
18589
  this.trace = void 0;
@@ -18649,7 +18666,7 @@ var SessionEmitter = class {
18649
18666
  // What the agent was asked. `TraceArgs.input` is construction-only — the SDK's
18650
18667
  // `update()` carries output alone — which is why `ingest` absorbs the user turn
18651
18668
  // before it calls this.
18652
- ...this.lastUserText ? { input: this.lastUserText } : {},
18669
+ ...this.lastUserText ? { input: capText(this.lastUserText) } : {},
18653
18670
  tags: [this.options.vendor, ...this.options.agentId ? ["subagent"] : []],
18654
18671
  metadata: {
18655
18672
  ...this.meta,
@@ -18718,7 +18735,7 @@ var SessionEmitter = class {
18718
18735
  buildInputMessages() {
18719
18736
  const messages = [];
18720
18737
  if (this.lastUserText !== void 0) {
18721
- messages.push({ role: "user", content: this.lastUserText });
18738
+ messages.push({ role: "user", content: capText(this.lastUserText) });
18722
18739
  }
18723
18740
  for (const result of this.pendingToolResults) {
18724
18741
  messages.push({ role: "tool", content: result });
@@ -18728,7 +18745,7 @@ var SessionEmitter = class {
18728
18745
  endGeneration() {
18729
18746
  if (!this.generation)
18730
18747
  return;
18731
- const output = this.outputText.join("\n");
18748
+ const output = capText(this.outputText.join("\n"));
18732
18749
  if (output)
18733
18750
  this.lastAssistantText = output;
18734
18751
  this.generation.end({
@@ -18770,6 +18787,25 @@ function loadCheckpoints(scope) {
18770
18787
  return {};
18771
18788
  }
18772
18789
  }
18790
+ function laggingTranscripts(checkpoints, now = statSize) {
18791
+ const paths = [];
18792
+ for (const [path, checkpoint] of Object.entries(checkpoints)) {
18793
+ if (path === SPOOL_KEY)
18794
+ continue;
18795
+ const size = now(path);
18796
+ if (size === void 0 || size === checkpoint.offset)
18797
+ continue;
18798
+ paths.push(path);
18799
+ }
18800
+ return paths;
18801
+ }
18802
+ function statSize(path) {
18803
+ try {
18804
+ return statSync2(path).size;
18805
+ } catch {
18806
+ return void 0;
18807
+ }
18808
+ }
18773
18809
  function orphanedScopes(vendor, scope) {
18774
18810
  const suffix = ".checkpoints.json";
18775
18811
  try {
@@ -18853,6 +18889,7 @@ function readNewLines(path, offset, maxBytes = DEFAULT_MAX_BYTES) {
18853
18889
  import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
18854
18890
  import { join as join7 } from "node:path";
18855
18891
  var STALE_MS = 5 * 60 * 1e3;
18892
+ var HEARTBEAT_MS = 60 * 1e3;
18856
18893
  function acquireLock(scope) {
18857
18894
  mkdirSync3(CONFIG_DIR, { recursive: true, mode: 448 });
18858
18895
  const path = join7(CONFIG_DIR, `${scope}.forwarder.lock`);
@@ -18874,7 +18911,17 @@ function acquireLock(scope) {
18874
18911
  } catch {
18875
18912
  return null;
18876
18913
  }
18877
- return () => rmSync(path, { force: true });
18914
+ const beat = setInterval(() => {
18915
+ try {
18916
+ writeFileSync3(path, JSON.stringify({ pid: process.pid, ts: Date.now() }), { mode: 384 });
18917
+ } catch {
18918
+ }
18919
+ }, HEARTBEAT_MS);
18920
+ beat.unref();
18921
+ return () => {
18922
+ clearInterval(beat);
18923
+ rmSync(path, { force: true });
18924
+ };
18878
18925
  }
18879
18926
  function isAlive(pid, kill = (p, s) => {
18880
18927
  process.kill(p, s);
@@ -18975,6 +19022,65 @@ function saveHealth(scope, health) {
18975
19022
  }
18976
19023
  }
18977
19024
 
19025
+ // packages/core/dist/forwarder/pending.js
19026
+ import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
19027
+ import { join as join9 } from "node:path";
19028
+ var MAX_PENDING_PATHS = 256;
19029
+ function pendingPath(scope) {
19030
+ return join9(CONFIG_DIR, `${scope}.pending.json`);
19031
+ }
19032
+ function loadPending(scope) {
19033
+ try {
19034
+ const backlog = JSON.parse(readFileSync6(pendingPath(scope), "utf8"));
19035
+ if (!Array.isArray(backlog.paths))
19036
+ return void 0;
19037
+ return backlog;
19038
+ } catch {
19039
+ return void 0;
19040
+ }
19041
+ }
19042
+ function pendingTranscripts(scope) {
19043
+ const backlog = loadPending(scope);
19044
+ if (!backlog)
19045
+ return [];
19046
+ return backlog.paths.filter((path) => {
19047
+ try {
19048
+ return existsSync3(path);
19049
+ } catch {
19050
+ return false;
19051
+ }
19052
+ });
19053
+ }
19054
+ function savePending(scope, paths, now = /* @__PURE__ */ new Date()) {
19055
+ try {
19056
+ const at = now.toISOString();
19057
+ const previous = loadPending(scope);
19058
+ const merged = [.../* @__PURE__ */ new Set([...previous?.paths ?? [], ...paths])];
19059
+ const kept = merged.slice(Math.max(0, merged.length - MAX_PENDING_PATHS));
19060
+ if (kept.length === 0)
19061
+ return;
19062
+ write(scope, { since: previous?.since ?? at, at, paths: kept });
19063
+ } catch {
19064
+ }
19065
+ }
19066
+ function clearPending(scope) {
19067
+ try {
19068
+ rmSync2(pendingPath(scope), { force: true });
19069
+ } catch {
19070
+ }
19071
+ }
19072
+ function write(scope, backlog) {
19073
+ mkdirSync5(CONFIG_DIR, { recursive: true, mode: 448 });
19074
+ const target = pendingPath(scope);
19075
+ const tmp = `${target}.tmp`;
19076
+ writeFileSync5(tmp, JSON.stringify(backlog), { mode: 384 });
19077
+ renameSync4(tmp, target);
19078
+ }
19079
+
19080
+ // packages/core/dist/forwarder/retry.js
19081
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
19082
+ import { join as join10 } from "node:path";
19083
+
18978
19084
  // packages/core/dist/forwarder/run.js
18979
19085
  import { readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
18980
19086
 
@@ -21810,7 +21916,14 @@ async function runForwarder(mapper, options = {}) {
21810
21916
  }
21811
21917
  }
21812
21918
  const drain = options.paths ? { paths: options.paths } : drainSpool(mapper, checkpoints);
21813
- const named = options.paths ? drain.paths : [.../* @__PURE__ */ new Set([...drain.paths, ...liveTranscripts(loadBeat(config.scope))])];
21919
+ const named = options.paths ? drain.paths : [
21920
+ .../* @__PURE__ */ new Set([
21921
+ ...drain.paths,
21922
+ ...liveTranscripts(loadBeat(config.scope)),
21923
+ ...pendingTranscripts(config.scope),
21924
+ ...laggingTranscripts(checkpoints)
21925
+ ])
21926
+ ];
21814
21927
  const paths = [...new Set(named.flatMap((path) => [path, ...related(mapper, path)]))];
21815
21928
  if (paths.length === 0) {
21816
21929
  if (drain.checkpoint)
@@ -21907,13 +22020,16 @@ async function runForwarder(mapper, options = {}) {
21907
22020
  result.emitted = { transcripts: result.transcripts, records: result.records };
21908
22021
  if (watch.failures() > 0) {
21909
22022
  result.ok = false;
22023
+ result.retryable = true;
21910
22024
  result.error = watch.reason() ?? "span export failed";
21911
22025
  result.transcripts = 0;
21912
22026
  result.records = 0;
22027
+ savePending(config.scope, paths);
21913
22028
  } else {
21914
22029
  if (drain.checkpoint)
21915
22030
  staged[SPOOL_KEY] = drain.checkpoint;
21916
22031
  saveCheckpoints(config.scope, staged);
22032
+ clearPending(config.scope);
21917
22033
  }
21918
22034
  const previous = loadHealth(config.scope);
21919
22035
  saveHealth(config.scope, {
@@ -21965,17 +22081,103 @@ function discoverTranscripts(mapper) {
21965
22081
  return found;
21966
22082
  }
21967
22083
 
22084
+ // packages/core/dist/forwarder/retry.js
22085
+ var RETRY_DELAYS_MS = [
22086
+ 3e4,
22087
+ 6e4,
22088
+ 12e4,
22089
+ 3e5,
22090
+ 6e5,
22091
+ 9e5,
22092
+ 18e5
22093
+ ];
22094
+ var CLAIM_STALE_MS = 45 * 60 * 1e3;
22095
+ function retryClaimPath(scope) {
22096
+ return join10(CONFIG_DIR, `${scope}.forwarder-retry.lock`);
22097
+ }
22098
+ function claimRetry(scope, now = Date.now()) {
22099
+ const path = retryClaimPath(scope);
22100
+ try {
22101
+ mkdirSync6(CONFIG_DIR, { recursive: true, mode: 448 });
22102
+ if (existsSync4(path)) {
22103
+ try {
22104
+ const held = JSON.parse(readFileSync7(path, "utf8"));
22105
+ if (isAlive(held.pid) && now - held.ts < CLAIM_STALE_MS)
22106
+ return null;
22107
+ } catch {
22108
+ }
22109
+ rmSync3(path, { force: true });
22110
+ }
22111
+ const stamp = () => {
22112
+ writeFileSync6(path, JSON.stringify({ pid: process.pid, ts: Date.now() }), { mode: 384 });
22113
+ };
22114
+ stamp();
22115
+ return {
22116
+ renew: () => {
22117
+ try {
22118
+ stamp();
22119
+ } catch {
22120
+ }
22121
+ },
22122
+ release: () => rmSync3(path, { force: true })
22123
+ };
22124
+ } catch {
22125
+ return null;
22126
+ }
22127
+ }
22128
+ function retriesEnabled() {
22129
+ const flag = process.env["DARKHUNT_FORWARDER_RETRY"];
22130
+ return flag !== "0" && flag !== "off" && flag !== "false";
22131
+ }
22132
+ function scopeOf(mapper, options) {
22133
+ try {
22134
+ return loadLocalConfig(mapper.vendor, options.profile !== void 0 ? { profile: options.profile } : {}).scope;
22135
+ } catch {
22136
+ return mapper.vendor;
22137
+ }
22138
+ }
22139
+ async function runForwarderWithRetry(mapper, options = {}) {
22140
+ const pass = options.pass ?? ((m, o) => runForwarder(m, o));
22141
+ const sleep2 = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
22142
+ const forwarding = {
22143
+ ...options.paths !== void 0 ? { paths: options.paths } : {},
22144
+ ...options.profile !== void 0 ? { profile: options.profile } : {}
22145
+ };
22146
+ let last = await pass(mapper, forwarding);
22147
+ if (!last.retryable || !retriesEnabled())
22148
+ return last;
22149
+ const claim = claimRetry(scopeOf(mapper, options));
22150
+ if (!claim)
22151
+ return last;
22152
+ try {
22153
+ const delays = options.delaysMs ?? RETRY_DELAYS_MS;
22154
+ for (const [index, delay] of delays.entries()) {
22155
+ options.onRetry?.(last, index + 1, delay);
22156
+ await sleep2(delay);
22157
+ claim.renew();
22158
+ last = await pass(mapper, forwarding);
22159
+ if (last.lockBusy)
22160
+ continue;
22161
+ if (!last.retryable)
22162
+ return last;
22163
+ }
22164
+ return last;
22165
+ } finally {
22166
+ claim.release();
22167
+ }
22168
+ }
22169
+
21968
22170
  // packages/core/dist/cli/status.js
21969
22171
  var SESSION_HOOK_STALE_MS = 30 * 60 * 1e3;
21970
22172
 
21971
22173
  // packages/core/dist/cli/enroll.js
21972
22174
  import { homedir as homedir3 } from "node:os";
21973
- import { join as join9 } from "node:path";
21974
- var CLI_CREDENTIALS_PATH = join9(homedir3(), ".darkhunt", "credentials.json");
22175
+ import { join as join11 } from "node:path";
22176
+ var CLI_CREDENTIALS_PATH = join11(homedir3(), ".darkhunt", "credentials.json");
21975
22177
 
21976
22178
  // adapters/codex/dist/transcript.js
21977
- import { readFileSync as readFileSync6 } from "node:fs";
21978
- import { basename, join as join10 } from "node:path";
22179
+ import { readFileSync as readFileSync8 } from "node:fs";
22180
+ import { basename, join as join12 } from "node:path";
21979
22181
  import { homedir as homedir4 } from "node:os";
21980
22182
  function str(value) {
21981
22183
  return typeof value === "string" ? value : void 0;
@@ -22119,7 +22321,7 @@ function stripSuffix(path) {
22119
22321
  var codexTranscript = {
22120
22322
  vendor: "codex",
22121
22323
  sessionRoots() {
22122
- return [join10(homedir4(), ".codex", "sessions")];
22324
+ return [join12(homedir4(), ".codex", "sessions")];
22123
22325
  },
22124
22326
  /**
22125
22327
  * `~/.codex/auth.json` -> the `email` claim of `tokens.id_token`.
@@ -22141,7 +22343,7 @@ var codexTranscript = {
22141
22343
  */
22142
22344
  resolveUserId() {
22143
22345
  try {
22144
- return emailFromAuth(readFileSync6(join10(homedir4(), ".codex", "auth.json"), "utf8"));
22346
+ return emailFromAuth(readFileSync8(join12(homedir4(), ".codex", "auth.json"), "utf8"));
22145
22347
  } catch {
22146
22348
  return void 0;
22147
22349
  }
@@ -22353,7 +22555,7 @@ var codexTranscript = {
22353
22555
 
22354
22556
  // adapters/codex/bin/forwarder.mjs
22355
22557
  try {
22356
- await runForwarder(codexTranscript);
22558
+ await runForwarderWithRetry(codexTranscript);
22357
22559
  } catch {
22358
22560
  process.exit(1);
22359
22561
  }
@@ -396,12 +396,16 @@ async function readStdin(stream) {
396
396
  return Buffer.concat(chunks).toString("utf8");
397
397
  }
398
398
 
399
+ // packages/core/dist/emit/emitter.js
400
+ var PAYLOAD_LIMIT = 128 * 1024;
401
+
399
402
  // packages/core/dist/forwarder/tail.js
400
403
  var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
401
404
  var LINE_CEILING = 64 * 1024 * 1024;
402
405
 
403
406
  // packages/core/dist/forwarder/lock.js
404
407
  var STALE_MS = 5 * 60 * 1e3;
408
+ var HEARTBEAT_MS = 60 * 1e3;
405
409
  var DEFAULT_LOCK_WAIT_MS = 60 * 1e3;
406
410
 
407
411
  // node_modules/@darkhunt-security/telemetry/package.json
@@ -1330,6 +1334,9 @@ var VALIDATORS = Object.freeze({
1330
1334
  // node_modules/@darkhunt-security/telemetry/dist/client.js
1331
1335
  var LIB_VERSION = package_default.version;
1332
1336
 
1337
+ // packages/core/dist/forwarder/retry.js
1338
+ var CLAIM_STALE_MS = 45 * 60 * 1e3;
1339
+
1333
1340
  // packages/core/dist/cli/status.js
1334
1341
  var SESSION_HOOK_STALE_MS = 30 * 60 * 1e3;
1335
1342
 
package/dist/bin/init.mjs CHANGED
@@ -52,12 +52,16 @@ var SPOOL_DIR = join4(CONFIG_DIR, "spool");
52
52
  // packages/core/dist/runtime/beat.js
53
53
  var BEAT_LIVE_MS = 30 * 60 * 1e3;
54
54
 
55
+ // packages/core/dist/emit/emitter.js
56
+ var PAYLOAD_LIMIT = 128 * 1024;
57
+
55
58
  // packages/core/dist/forwarder/tail.js
56
59
  var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
57
60
  var LINE_CEILING = 64 * 1024 * 1024;
58
61
 
59
62
  // packages/core/dist/forwarder/lock.js
60
63
  var STALE_MS = 5 * 60 * 1e3;
64
+ var HEARTBEAT_MS = 60 * 1e3;
61
65
  var DEFAULT_LOCK_WAIT_MS = 60 * 1e3;
62
66
 
63
67
  // node_modules/@darkhunt-security/telemetry/package.json
@@ -986,6 +990,9 @@ var VALIDATORS = Object.freeze({
986
990
  // node_modules/@darkhunt-security/telemetry/dist/client.js
987
991
  var LIB_VERSION = package_default.version;
988
992
 
993
+ // packages/core/dist/forwarder/retry.js
994
+ var CLAIM_STALE_MS = 45 * 60 * 1e3;
995
+
989
996
  // packages/core/dist/cli/init.js
990
997
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
991
998
  function init(argv) {
@@ -198,12 +198,16 @@ async function readStdin(stream) {
198
198
  return Buffer.concat(chunks).toString("utf8");
199
199
  }
200
200
 
201
+ // packages/core/dist/emit/emitter.js
202
+ var PAYLOAD_LIMIT = 128 * 1024;
203
+
201
204
  // packages/core/dist/forwarder/tail.js
202
205
  var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
203
206
  var LINE_CEILING = 64 * 1024 * 1024;
204
207
 
205
208
  // packages/core/dist/forwarder/lock.js
206
209
  var STALE_MS = 5 * 60 * 1e3;
210
+ var HEARTBEAT_MS = 60 * 1e3;
207
211
  var DEFAULT_LOCK_WAIT_MS = 60 * 1e3;
208
212
 
209
213
  // node_modules/@darkhunt-security/telemetry/package.json
@@ -1132,6 +1136,9 @@ var VALIDATORS = Object.freeze({
1132
1136
  // node_modules/@darkhunt-security/telemetry/dist/client.js
1133
1137
  var LIB_VERSION = package_default.version;
1134
1138
 
1139
+ // packages/core/dist/forwarder/retry.js
1140
+ var CLAIM_STALE_MS = 45 * 60 * 1e3;
1141
+
1135
1142
  // packages/core/dist/cli/status.js
1136
1143
  var SESSION_HOOK_STALE_MS = 30 * 60 * 1e3;
1137
1144
 
@@ -3,8 +3,8 @@ import { createRequire as __dhCreateRequire } from 'node:module';
3
3
  const require = __dhCreateRequire(import.meta.url);
4
4
 
5
5
  // adapters/codex/bin/status.mjs
6
- import { readFileSync as readFileSync8 } from "node:fs";
7
- import { dirname, join as join12 } from "node:path";
6
+ import { readFileSync as readFileSync9 } from "node:fs";
7
+ import { dirname, join as join13 } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
10
10
  // packages/core/dist/config/local.js
@@ -173,6 +173,9 @@ function loadBeat(scope) {
173
173
  }
174
174
  }
175
175
 
176
+ // packages/core/dist/emit/emitter.js
177
+ var PAYLOAD_LIMIT = 128 * 1024;
178
+
176
179
  // packages/core/dist/forwarder/checkpoint.js
177
180
  import { copyFileSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, renameSync as renameSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
178
181
  import { join as join6 } from "node:path";
@@ -193,6 +196,7 @@ var LINE_CEILING = 64 * 1024 * 1024;
193
196
 
194
197
  // packages/core/dist/forwarder/lock.js
195
198
  var STALE_MS = 5 * 60 * 1e3;
199
+ var HEARTBEAT_MS = 60 * 1e3;
196
200
  var DEFAULT_LOCK_WAIT_MS = 60 * 1e3;
197
201
 
198
202
  // packages/core/dist/forwarder/health.js
@@ -209,6 +213,35 @@ function loadHealth(scope) {
209
213
  }
210
214
  }
211
215
 
216
+ // packages/core/dist/forwarder/pending.js
217
+ import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync4, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
218
+ import { join as join8 } from "node:path";
219
+ function pendingPath(scope) {
220
+ return join8(CONFIG_DIR, `${scope}.pending.json`);
221
+ }
222
+ function loadPending(scope) {
223
+ try {
224
+ const backlog = JSON.parse(readFileSync5(pendingPath(scope), "utf8"));
225
+ if (!Array.isArray(backlog.paths))
226
+ return void 0;
227
+ return backlog;
228
+ } catch {
229
+ return void 0;
230
+ }
231
+ }
232
+ function pendingTranscripts(scope) {
233
+ const backlog = loadPending(scope);
234
+ if (!backlog)
235
+ return [];
236
+ return backlog.paths.filter((path) => {
237
+ try {
238
+ return existsSync2(path);
239
+ } catch {
240
+ return false;
241
+ }
242
+ });
243
+ }
244
+
212
245
  // node_modules/@darkhunt-security/telemetry/package.json
213
246
  var package_default = {
214
247
  name: "@darkhunt-security/telemetry",
@@ -1135,9 +1168,12 @@ var VALIDATORS = Object.freeze({
1135
1168
  // node_modules/@darkhunt-security/telemetry/dist/client.js
1136
1169
  var LIB_VERSION = package_default.version;
1137
1170
 
1171
+ // packages/core/dist/forwarder/retry.js
1172
+ var CLAIM_STALE_MS = 45 * 60 * 1e3;
1173
+
1138
1174
  // packages/core/dist/cli/status.js
1139
- import { existsSync as existsSync2, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
1140
- import { join as join8 } from "node:path";
1175
+ import { existsSync as existsSync3, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
1176
+ import { join as join9 } from "node:path";
1141
1177
  var SESSION_HOOK_STALE_MS = 30 * 60 * 1e3;
1142
1178
  function countSubagents(mapper, checkpoints) {
1143
1179
  if (!mapper?.subagentOf || !mapper.sessionRoots)
@@ -1152,7 +1188,7 @@ function countSubagents(mapper, checkpoints) {
1152
1188
  return;
1153
1189
  }
1154
1190
  for (const entry of entries) {
1155
- const full = join8(dir, entry.name);
1191
+ const full = join9(dir, entry.name);
1156
1192
  if (entry.isDirectory()) {
1157
1193
  walk(full);
1158
1194
  continue;
@@ -1177,16 +1213,19 @@ function status(vendor, mapper) {
1177
1213
  for (const [path, cp] of Object.entries(checkpoints)) {
1178
1214
  if (path === "::spool::")
1179
1215
  continue;
1180
- const size = existsSync2(path) ? statSync3(path).size : 0;
1216
+ const size = existsSync3(path) ? statSync3(path).size : 0;
1181
1217
  transcripts.push({ path, shipped: cp.offset, size, lag: Math.max(0, size - cp.offset) });
1182
1218
  }
1183
1219
  const subagents = countSubagents(mapper, checkpoints);
1184
1220
  const lastPass = loadHealth(config.scope);
1221
+ const backlog = loadPending(config.scope);
1222
+ const heldTranscripts = pendingTranscripts(config.scope).length;
1223
+ const pending = backlog && heldTranscripts > 0 ? { transcripts: heldTranscripts, since: backlog.since, at: backlog.at } : void 0;
1185
1224
  const spool = spoolPath(vendor);
1186
1225
  let spoolEvents = 0;
1187
1226
  let spoolLastAt;
1188
- if (existsSync2(spool)) {
1189
- const lines = readFileSync5(spool, "utf8").split("\n").filter(Boolean);
1227
+ if (existsSync3(spool)) {
1228
+ const lines = readFileSync6(spool, "utf8").split("\n").filter(Boolean);
1190
1229
  spoolEvents = lines.length;
1191
1230
  const last = lines[lines.length - 1];
1192
1231
  if (last) {
@@ -1224,19 +1263,20 @@ function status(vendor, mapper) {
1224
1263
  transcripts,
1225
1264
  totalLag: transcripts.reduce((sum, t) => sum + t.lag, 0),
1226
1265
  ...lastPass !== void 0 ? { lastPass } : {},
1266
+ ...pending !== void 0 ? { pending } : {},
1227
1267
  ...subagents !== void 0 ? { subagents } : {},
1228
- lockHeld: existsSync2(spool.replace(/spool\/.*$/, `${config.scope}.forwarder.lock`))
1268
+ lockHeld: existsSync3(spool.replace(/spool\/.*$/, `${config.scope}.forwarder.lock`))
1229
1269
  };
1230
1270
  }
1231
1271
 
1232
1272
  // packages/core/dist/cli/enroll.js
1233
1273
  import { homedir as homedir3 } from "node:os";
1234
- import { join as join9 } from "node:path";
1235
- var CLI_CREDENTIALS_PATH = join9(homedir3(), ".darkhunt", "credentials.json");
1274
+ import { join as join10 } from "node:path";
1275
+ var CLI_CREDENTIALS_PATH = join10(homedir3(), ".darkhunt", "credentials.json");
1236
1276
 
1237
1277
  // adapters/codex/dist/transcript.js
1238
- import { readFileSync as readFileSync6 } from "node:fs";
1239
- import { basename, join as join10 } from "node:path";
1278
+ import { readFileSync as readFileSync7 } from "node:fs";
1279
+ import { basename, join as join11 } from "node:path";
1240
1280
  import { homedir as homedir4 } from "node:os";
1241
1281
  function str(value) {
1242
1282
  return typeof value === "string" ? value : void 0;
@@ -1380,7 +1420,7 @@ function stripSuffix(path) {
1380
1420
  var codexTranscript = {
1381
1421
  vendor: "codex",
1382
1422
  sessionRoots() {
1383
- return [join10(homedir4(), ".codex", "sessions")];
1423
+ return [join11(homedir4(), ".codex", "sessions")];
1384
1424
  },
1385
1425
  /**
1386
1426
  * `~/.codex/auth.json` -> the `email` claim of `tokens.id_token`.
@@ -1402,7 +1442,7 @@ var codexTranscript = {
1402
1442
  */
1403
1443
  resolveUserId() {
1404
1444
  try {
1405
- return emailFromAuth(readFileSync6(join10(homedir4(), ".codex", "auth.json"), "utf8"));
1445
+ return emailFromAuth(readFileSync7(join11(homedir4(), ".codex", "auth.json"), "utf8"));
1406
1446
  } catch {
1407
1447
  return void 0;
1408
1448
  }
@@ -1613,14 +1653,14 @@ var codexTranscript = {
1613
1653
  };
1614
1654
 
1615
1655
  // adapters/codex/bin/lib/launchers.mjs
1616
- import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
1656
+ import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
1617
1657
  import { homedir as homedir5 } from "node:os";
1618
- import { join as join11 } from "node:path";
1658
+ import { join as join12 } from "node:path";
1619
1659
  var LAUNCHERS = {
1620
1660
  "darkhunt-codex-spool": "spool.mjs",
1621
1661
  "darkhunt-codex-guard": "guard.mjs"
1622
1662
  };
1623
- var BIN_DIR = join11(homedir5(), ".local", "bin");
1663
+ var BIN_DIR = join12(homedir5(), ".local", "bin");
1624
1664
  var script = (target) => `#!/bin/sh
1625
1665
  # Installed by darkhunt endpoint-plugins. Resolves the newest installed
1626
1666
  # plugin version at run time; do not hard-code a version here.
@@ -1632,14 +1672,14 @@ function launcherState(binDir = BIN_DIR, path = process.env["PATH"] ?? "") {
1632
1672
  const missing = [];
1633
1673
  const stale = [];
1634
1674
  for (const [name, target] of Object.entries(LAUNCHERS)) {
1635
- const file = join11(binDir, name);
1636
- if (!existsSync3(file)) {
1675
+ const file = join12(binDir, name);
1676
+ if (!existsSync4(file)) {
1637
1677
  missing.push(name);
1638
1678
  continue;
1639
1679
  }
1640
1680
  let body = "";
1641
1681
  try {
1642
- body = readFileSync7(file, "utf8");
1682
+ body = readFileSync8(file, "utf8");
1643
1683
  } catch {
1644
1684
  }
1645
1685
  if (body !== script(target)) stale.push(name);
@@ -1674,10 +1714,10 @@ try {
1674
1714
  const s = status("codex", codexTranscript);
1675
1715
  const n = (x) => x.toLocaleString();
1676
1716
  console.log(`darkhunt endpoint status \u2014 codex`);
1677
- const root = join12(dirname(fileURLToPath(import.meta.url)), "..", "..");
1717
+ const root = join13(dirname(fileURLToPath(import.meta.url)), "..", "..");
1678
1718
  let installed = "unknown";
1679
1719
  try {
1680
- installed = JSON.parse(readFileSync8(join12(root, "package.json"), "utf8")).version;
1720
+ installed = JSON.parse(readFileSync9(join13(root, "package.json"), "utf8")).version;
1681
1721
  } catch {
1682
1722
  }
1683
1723
  console.log(` plugin ${installed} ${root}`);
@@ -1725,6 +1765,11 @@ try {
1725
1765
  );
1726
1766
  }
1727
1767
  }
1768
+ if (s.pending) {
1769
+ console.log(
1770
+ ` held ${s.pending.transcripts} transcript(s) waiting on the platform since ${s.pending.since}`
1771
+ );
1772
+ }
1728
1773
  if (s.transcripts.length === 0) {
1729
1774
  console.log(" transcripts none shipped yet");
1730
1775
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkhunt-security/endpoint-codex",
3
- "version": "0.9.17",
3
+ "version": "0.9.18",
4
4
  "type": "module",
5
5
  "description": "Darkhunt endpoint adapter for Codex CLI: hook codec, rollout transcript mapper, plugin manifest.",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "devDependencies": {
16
16
  "@darkhunt-security/endpoint-contracts": "0.9.3",
17
- "@darkhunt-security/endpoint-core": "0.9.3"
17
+ "@darkhunt-security/endpoint-core": "0.9.4"
18
18
  },
19
19
  "publishConfig": {
20
20
  "access": "public",