@darkhunt-security/endpoint-codex 0.9.2 → 0.9.4

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.2",
3
+ "version": "0.9.4",
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": {
package/README.md CHANGED
@@ -44,11 +44,24 @@ to write. Enrolment writes them; a pasted config cannot. Run this once after pas
44
44
  `/darkhunt-guard:status` reports the launchers, so you never have to take this on trust —
45
45
  a machine missing them looks perfectly configured everywhere else and captures nothing.
46
46
 
47
+ ## Upgrade
48
+
49
+ ```
50
+ codex plugin add darkhunt-guard@darkhunt
51
+ ```
52
+
53
+ Re-adding re-resolves the published version. The new one loads on your **next** Codex
54
+ CLI session, and the launchers need no attention — they resolve the newest installed
55
+ version at run time. `/darkhunt-guard:status` prints the version actually running,
56
+ which is the only reliable answer: an update that resolved against a registry without
57
+ the new version reports success and changes nothing.
58
+
47
59
  ## What it captures
48
60
 
49
61
  The session transcript, mapped to spans: user and assistant messages, thinking blocks,
50
62
  tool calls paired with their results, token usage, attachment metadata (never the bytes),
51
- and the subagent topology as linked child traces.
63
+ and every subagent's own rollout — Codex writes one per subagent, linked to its
64
+ parent through `parent_thread_id` and labelled with the agent.
52
65
 
53
66
  Reading the transcript rather than instrumenting the agent is what makes this complete:
54
67
  there is no code path in Codex CLI we could have missed.
@@ -18350,7 +18350,7 @@ var SessionEmitter = class {
18350
18350
  seeded(parts, create) {
18351
18351
  if (!this.options.ids)
18352
18352
  return create();
18353
- return this.options.ids.under(seed(this.options.sessionId, ...parts), create);
18353
+ return this.options.ids.under(seed(this.options.seedKey ?? this.options.sessionId, ...parts), create);
18354
18354
  }
18355
18355
  ingest(record) {
18356
18356
  if (record.kind === "session_meta") {
@@ -18358,6 +18358,9 @@ var SessionEmitter = class {
18358
18358
  this.title = record.meta.title;
18359
18359
  if (record.meta?.model)
18360
18360
  this.model = record.meta.model;
18361
+ if (record.meta?.parentSessionId && !this.trace) {
18362
+ this.grouping = record.meta.parentSessionId;
18363
+ }
18361
18364
  for (const [key, value] of Object.entries(record.meta ?? {})) {
18362
18365
  if (typeof value === "string" && key !== "title")
18363
18366
  this.meta[key] = value;
@@ -18471,14 +18474,31 @@ var SessionEmitter = class {
18471
18474
  }
18472
18475
  await this.client.flush();
18473
18476
  }
18477
+ defaultTraceName() {
18478
+ const agentType = this.options.agentType ?? this.meta["agentType"];
18479
+ if (agentType)
18480
+ return `subagent: ${agentType}`;
18481
+ if (this.options.agentId === void 0) {
18482
+ return this.grouping ? "subagent" : `${this.options.vendor} session`;
18483
+ }
18484
+ return this.options.agentType ? `subagent: ${this.options.agentType}` : `subagent ${this.options.agentId}`;
18485
+ }
18486
+ /** The session traces group under. Set from the path, or from `session_meta`. */
18487
+ grouping;
18474
18488
  ensureTrace(ts) {
18475
18489
  if (!this.trace) {
18476
18490
  this.trace = this.seeded(["trace", this.options.passKey ?? "0"], () => this.client.trace({
18477
- name: this.title ?? `${this.options.vendor} session`,
18478
- sessionId: this.options.sessionId,
18491
+ // A subagent's transcript has no title of its own, and "claude-code session"
18492
+ // beside its parent reads as a second session rather than part of one.
18493
+ name: this.title ?? this.defaultTraceName(),
18494
+ sessionId: this.grouping ?? this.options.sessionId,
18479
18495
  startTime: ts,
18480
- tags: [this.options.vendor],
18481
- metadata: this.meta,
18496
+ tags: [this.options.vendor, ...this.options.agentId ? ["subagent"] : []],
18497
+ metadata: {
18498
+ ...this.meta,
18499
+ ...this.options.agentId !== void 0 ? { agentId: this.options.agentId } : {},
18500
+ ...this.options.agentType !== void 0 ? { agentType: this.options.agentType } : {}
18501
+ },
18482
18502
  ...this.options.userId !== void 0 ? { userId: this.options.userId } : {},
18483
18503
  ...this.options.userEmail !== void 0 ? { userEmail: this.options.userEmail } : {},
18484
18504
  ...this.options.environment !== void 0 ? { environment: this.options.environment } : {}
@@ -21585,7 +21605,8 @@ async function runForwarder(mapper, options = {}) {
21585
21605
  }
21586
21606
  }
21587
21607
  const drain = options.paths ? { paths: options.paths } : drainSpool(mapper, checkpoints);
21588
- const paths2 = options.paths ? drain.paths : [.../* @__PURE__ */ new Set([...drain.paths, ...liveTranscripts(loadBeat(config.scope))])];
21608
+ const named = options.paths ? drain.paths : [.../* @__PURE__ */ new Set([...drain.paths, ...liveTranscripts(loadBeat(config.scope))])];
21609
+ const paths2 = [...new Set(named.flatMap((path) => [path, ...related(mapper, path)]))];
21589
21610
  if (paths2.length === 0) {
21590
21611
  if (drain.checkpoint)
21591
21612
  checkpoints[SPOOL_KEY] = drain.checkpoint;
@@ -21620,10 +21641,15 @@ async function runForwarder(mapper, options = {}) {
21620
21641
  try {
21621
21642
  const from = resumeOffset(checkpoints, path);
21622
21643
  const carried = resumeTurn(checkpoints, path, from);
21644
+ const own = mapper.sessionIdFor(path);
21645
+ const sub = subagentOf(mapper, path);
21623
21646
  const emitter = new SessionEmitter(client, {
21624
- sessionId: mapper.sessionIdFor(path),
21647
+ sessionId: sub?.sessionId ?? own,
21648
+ seedKey: own,
21625
21649
  vendor: mapper.vendor,
21626
21650
  passKey: String(from),
21651
+ ...sub?.agentId !== void 0 ? { agentId: sub.agentId } : {},
21652
+ ...sub?.agentType !== void 0 ? { agentType: sub.agentType } : {},
21627
21653
  ...ids !== void 0 ? { ids } : {},
21628
21654
  ...userId !== void 0 ? { userId } : {},
21629
21655
  ...carried !== void 0 ? { resumeTurn: carried } : {}
@@ -21707,6 +21733,20 @@ function withinDays(paths2, days) {
21707
21733
  }
21708
21734
  });
21709
21735
  }
21736
+ function related(mapper, path) {
21737
+ try {
21738
+ return mapper.relatedTranscripts?.(path) ?? [];
21739
+ } catch {
21740
+ return [];
21741
+ }
21742
+ }
21743
+ function subagentOf(mapper, path) {
21744
+ try {
21745
+ return mapper.subagentOf?.(path);
21746
+ } catch {
21747
+ return void 0;
21748
+ }
21749
+ }
21710
21750
  function discoverTranscripts(mapper) {
21711
21751
  const found = [];
21712
21752
  const walk = (dir) => {
@@ -21814,7 +21854,14 @@ var codexTranscript = {
21814
21854
  meta: {
21815
21855
  ...str(payload["cwd"]) !== void 0 ? { cwd: str(payload["cwd"]) } : {},
21816
21856
  ...str(payload["cli_version"]) !== void 0 ? { version: str(payload["cli_version"]) } : {},
21817
- ...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {}
21857
+ ...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {},
21858
+ // A Codex subagent is a rollout of its own, a peer of its parent in the
21859
+ // same date directory — nothing in the path says whose it is. These three
21860
+ // are the only link, and unlike Claude Code's layout they are the vendor's
21861
+ // own documented session metadata rather than a guess.
21862
+ ...str(payload["parent_thread_id"]) !== void 0 ? { parentSessionId: str(payload["parent_thread_id"]) } : {},
21863
+ ...str(payload["agent_nickname"]) !== void 0 ? { agentType: str(payload["agent_nickname"]) } : {},
21864
+ ...str(payload["agent_path"]) !== void 0 ? { agentPath: str(payload["agent_path"]) } : {}
21818
21865
  }
21819
21866
  }
21820
21867
  ];
@@ -18350,7 +18350,7 @@ var SessionEmitter = class {
18350
18350
  seeded(parts, create) {
18351
18351
  if (!this.options.ids)
18352
18352
  return create();
18353
- return this.options.ids.under(seed(this.options.sessionId, ...parts), create);
18353
+ return this.options.ids.under(seed(this.options.seedKey ?? this.options.sessionId, ...parts), create);
18354
18354
  }
18355
18355
  ingest(record) {
18356
18356
  if (record.kind === "session_meta") {
@@ -18358,6 +18358,9 @@ var SessionEmitter = class {
18358
18358
  this.title = record.meta.title;
18359
18359
  if (record.meta?.model)
18360
18360
  this.model = record.meta.model;
18361
+ if (record.meta?.parentSessionId && !this.trace) {
18362
+ this.grouping = record.meta.parentSessionId;
18363
+ }
18361
18364
  for (const [key, value] of Object.entries(record.meta ?? {})) {
18362
18365
  if (typeof value === "string" && key !== "title")
18363
18366
  this.meta[key] = value;
@@ -18471,14 +18474,31 @@ var SessionEmitter = class {
18471
18474
  }
18472
18475
  await this.client.flush();
18473
18476
  }
18477
+ defaultTraceName() {
18478
+ const agentType = this.options.agentType ?? this.meta["agentType"];
18479
+ if (agentType)
18480
+ return `subagent: ${agentType}`;
18481
+ if (this.options.agentId === void 0) {
18482
+ return this.grouping ? "subagent" : `${this.options.vendor} session`;
18483
+ }
18484
+ return this.options.agentType ? `subagent: ${this.options.agentType}` : `subagent ${this.options.agentId}`;
18485
+ }
18486
+ /** The session traces group under. Set from the path, or from `session_meta`. */
18487
+ grouping;
18474
18488
  ensureTrace(ts) {
18475
18489
  if (!this.trace) {
18476
18490
  this.trace = this.seeded(["trace", this.options.passKey ?? "0"], () => this.client.trace({
18477
- name: this.title ?? `${this.options.vendor} session`,
18478
- sessionId: this.options.sessionId,
18491
+ // A subagent's transcript has no title of its own, and "claude-code session"
18492
+ // beside its parent reads as a second session rather than part of one.
18493
+ name: this.title ?? this.defaultTraceName(),
18494
+ sessionId: this.grouping ?? this.options.sessionId,
18479
18495
  startTime: ts,
18480
- tags: [this.options.vendor],
18481
- metadata: this.meta,
18496
+ tags: [this.options.vendor, ...this.options.agentId ? ["subagent"] : []],
18497
+ metadata: {
18498
+ ...this.meta,
18499
+ ...this.options.agentId !== void 0 ? { agentId: this.options.agentId } : {},
18500
+ ...this.options.agentType !== void 0 ? { agentType: this.options.agentType } : {}
18501
+ },
18482
18502
  ...this.options.userId !== void 0 ? { userId: this.options.userId } : {},
18483
18503
  ...this.options.userEmail !== void 0 ? { userEmail: this.options.userEmail } : {},
18484
18504
  ...this.options.environment !== void 0 ? { environment: this.options.environment } : {}
@@ -21585,7 +21605,8 @@ async function runForwarder(mapper, options = {}) {
21585
21605
  }
21586
21606
  }
21587
21607
  const drain = options.paths ? { paths: options.paths } : drainSpool(mapper, checkpoints);
21588
- const paths = options.paths ? drain.paths : [.../* @__PURE__ */ new Set([...drain.paths, ...liveTranscripts(loadBeat(config.scope))])];
21608
+ const named = options.paths ? drain.paths : [.../* @__PURE__ */ new Set([...drain.paths, ...liveTranscripts(loadBeat(config.scope))])];
21609
+ const paths = [...new Set(named.flatMap((path) => [path, ...related(mapper, path)]))];
21589
21610
  if (paths.length === 0) {
21590
21611
  if (drain.checkpoint)
21591
21612
  checkpoints[SPOOL_KEY] = drain.checkpoint;
@@ -21620,10 +21641,15 @@ async function runForwarder(mapper, options = {}) {
21620
21641
  try {
21621
21642
  const from = resumeOffset(checkpoints, path);
21622
21643
  const carried = resumeTurn(checkpoints, path, from);
21644
+ const own = mapper.sessionIdFor(path);
21645
+ const sub = subagentOf(mapper, path);
21623
21646
  const emitter = new SessionEmitter(client, {
21624
- sessionId: mapper.sessionIdFor(path),
21647
+ sessionId: sub?.sessionId ?? own,
21648
+ seedKey: own,
21625
21649
  vendor: mapper.vendor,
21626
21650
  passKey: String(from),
21651
+ ...sub?.agentId !== void 0 ? { agentId: sub.agentId } : {},
21652
+ ...sub?.agentType !== void 0 ? { agentType: sub.agentType } : {},
21627
21653
  ...ids !== void 0 ? { ids } : {},
21628
21654
  ...userId !== void 0 ? { userId } : {},
21629
21655
  ...carried !== void 0 ? { resumeTurn: carried } : {}
@@ -21694,6 +21720,20 @@ async function runForwarder(mapper, options = {}) {
21694
21720
  release();
21695
21721
  }
21696
21722
  }
21723
+ function related(mapper, path) {
21724
+ try {
21725
+ return mapper.relatedTranscripts?.(path) ?? [];
21726
+ } catch {
21727
+ return [];
21728
+ }
21729
+ }
21730
+ function subagentOf(mapper, path) {
21731
+ try {
21732
+ return mapper.subagentOf?.(path);
21733
+ } catch {
21734
+ return void 0;
21735
+ }
21736
+ }
21697
21737
  function discoverTranscripts(mapper) {
21698
21738
  const found = [];
21699
21739
  const walk = (dir) => {
@@ -21801,7 +21841,14 @@ var codexTranscript = {
21801
21841
  meta: {
21802
21842
  ...str(payload["cwd"]) !== void 0 ? { cwd: str(payload["cwd"]) } : {},
21803
21843
  ...str(payload["cli_version"]) !== void 0 ? { version: str(payload["cli_version"]) } : {},
21804
- ...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {}
21844
+ ...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {},
21845
+ // A Codex subagent is a rollout of its own, a peer of its parent in the
21846
+ // same date directory — nothing in the path says whose it is. These three
21847
+ // are the only link, and unlike Claude Code's layout they are the vendor's
21848
+ // own documented session metadata rather than a guess.
21849
+ ...str(payload["parent_thread_id"]) !== void 0 ? { parentSessionId: str(payload["parent_thread_id"]) } : {},
21850
+ ...str(payload["agent_nickname"]) !== void 0 ? { agentType: str(payload["agent_nickname"]) } : {},
21851
+ ...str(payload["agent_path"]) !== void 0 ? { agentPath: str(payload["agent_path"]) } : {}
21805
21852
  }
21806
21853
  }
21807
21854
  ];
@@ -2,6 +2,11 @@
2
2
  import { createRequire as __dhCreateRequire } from 'node:module';
3
3
  const require = __dhCreateRequire(import.meta.url);
4
4
 
5
+ // adapters/codex/bin/status.mjs
6
+ import { readFileSync as readFileSync7 } from "node:fs";
7
+ import { dirname, join as join12 } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
5
10
  // packages/core/dist/config/local.js
6
11
  import { existsSync, readFileSync, statSync } from "node:fs";
7
12
  import { join as join3 } from "node:path";
@@ -1119,8 +1124,40 @@ var VALIDATORS = Object.freeze({
1119
1124
  var LIB_VERSION = package_default.version;
1120
1125
 
1121
1126
  // packages/core/dist/cli/status.js
1122
- import { existsSync as existsSync2, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
1127
+ import { existsSync as existsSync2, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
1128
+ import { join as join8 } from "node:path";
1123
1129
  var SESSION_HOOK_STALE_MS = 30 * 60 * 1e3;
1130
+ function countSubagents(mapper, checkpoints) {
1131
+ if (!mapper?.subagentOf || !mapper.sessionRoots)
1132
+ return void 0;
1133
+ let found = 0;
1134
+ let shipped = 0;
1135
+ const walk = (dir) => {
1136
+ let entries;
1137
+ try {
1138
+ entries = readdirSync2(dir, { withFileTypes: true });
1139
+ } catch {
1140
+ return;
1141
+ }
1142
+ for (const entry of entries) {
1143
+ const full = join8(dir, entry.name);
1144
+ if (entry.isDirectory()) {
1145
+ walk(full);
1146
+ continue;
1147
+ }
1148
+ if (!entry.name.endsWith(".jsonl"))
1149
+ continue;
1150
+ if (!mapper.subagentOf?.(full))
1151
+ continue;
1152
+ found++;
1153
+ if (checkpoints[full])
1154
+ shipped++;
1155
+ }
1156
+ };
1157
+ for (const root of mapper.sessionRoots())
1158
+ walk(root);
1159
+ return { found, shipped };
1160
+ }
1124
1161
  function status(vendor, mapper) {
1125
1162
  const config = loadLocalConfig(vendor);
1126
1163
  const checkpoints = loadCheckpoints(config.scope);
@@ -1131,6 +1168,7 @@ function status(vendor, mapper) {
1131
1168
  const size = existsSync2(path) ? statSync3(path).size : 0;
1132
1169
  transcripts.push({ path, shipped: cp.offset, size, lag: Math.max(0, size - cp.offset) });
1133
1170
  }
1171
+ const subagents = countSubagents(mapper, checkpoints);
1134
1172
  const lastPass = loadHealth(config.scope);
1135
1173
  const spool = spoolPath(vendor);
1136
1174
  let spoolEvents = 0;
@@ -1171,17 +1209,18 @@ function status(vendor, mapper) {
1171
1209
  transcripts,
1172
1210
  totalLag: transcripts.reduce((sum, t) => sum + t.lag, 0),
1173
1211
  ...lastPass !== void 0 ? { lastPass } : {},
1212
+ ...subagents !== void 0 ? { subagents } : {},
1174
1213
  lockHeld: existsSync2(spool.replace(/spool\/.*$/, `${config.scope}.forwarder.lock`))
1175
1214
  };
1176
1215
  }
1177
1216
 
1178
1217
  // packages/core/dist/cli/enroll.js
1179
1218
  import { homedir as homedir3 } from "node:os";
1180
- import { join as join8 } from "node:path";
1181
- var CLI_CREDENTIALS_PATH = join8(homedir3(), ".darkhunt", "credentials.json");
1219
+ import { join as join9 } from "node:path";
1220
+ var CLI_CREDENTIALS_PATH = join9(homedir3(), ".darkhunt", "credentials.json");
1182
1221
 
1183
1222
  // adapters/codex/dist/transcript.js
1184
- import { basename, join as join9 } from "node:path";
1223
+ import { basename, join as join10 } from "node:path";
1185
1224
  import { homedir as homedir4 } from "node:os";
1186
1225
  function str(value) {
1187
1226
  return typeof value === "string" ? value : void 0;
@@ -1220,7 +1259,7 @@ function textOf(content) {
1220
1259
  var codexTranscript = {
1221
1260
  vendor: "codex",
1222
1261
  sessionRoots() {
1223
- return [join9(homedir4(), ".codex", "sessions")];
1262
+ return [join10(homedir4(), ".codex", "sessions")];
1224
1263
  },
1225
1264
  // No `resolveUserId` yet, so Codex traces fall back to the configured `userId`.
1226
1265
  // The identity exists but not as a plain field: `~/.codex/auth.json` holds
@@ -1257,7 +1296,14 @@ var codexTranscript = {
1257
1296
  meta: {
1258
1297
  ...str(payload["cwd"]) !== void 0 ? { cwd: str(payload["cwd"]) } : {},
1259
1298
  ...str(payload["cli_version"]) !== void 0 ? { version: str(payload["cli_version"]) } : {},
1260
- ...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {}
1299
+ ...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {},
1300
+ // A Codex subagent is a rollout of its own, a peer of its parent in the
1301
+ // same date directory — nothing in the path says whose it is. These three
1302
+ // are the only link, and unlike Claude Code's layout they are the vendor's
1303
+ // own documented session metadata rather than a guess.
1304
+ ...str(payload["parent_thread_id"]) !== void 0 ? { parentSessionId: str(payload["parent_thread_id"]) } : {},
1305
+ ...str(payload["agent_nickname"]) !== void 0 ? { agentType: str(payload["agent_nickname"]) } : {},
1306
+ ...str(payload["agent_path"]) !== void 0 ? { agentPath: str(payload["agent_path"]) } : {}
1261
1307
  }
1262
1308
  }
1263
1309
  ];
@@ -1321,12 +1367,12 @@ var codexTranscript = {
1321
1367
  // adapters/codex/bin/lib/launchers.mjs
1322
1368
  import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
1323
1369
  import { homedir as homedir5 } from "node:os";
1324
- import { join as join10 } from "node:path";
1370
+ import { join as join11 } from "node:path";
1325
1371
  var LAUNCHERS = {
1326
1372
  "darkhunt-codex-spool": "spool.mjs",
1327
1373
  "darkhunt-codex-guard": "guard.mjs"
1328
1374
  };
1329
- var BIN_DIR = join10(homedir5(), ".local", "bin");
1375
+ var BIN_DIR = join11(homedir5(), ".local", "bin");
1330
1376
  var script = (target) => `#!/bin/sh
1331
1377
  # Installed by darkhunt endpoint-plugins. Resolves the newest installed
1332
1378
  # plugin version at run time; do not hard-code a version here.
@@ -1338,7 +1384,7 @@ function launcherState(binDir = BIN_DIR, path = process.env["PATH"] ?? "") {
1338
1384
  const missing = [];
1339
1385
  const stale = [];
1340
1386
  for (const [name, target] of Object.entries(LAUNCHERS)) {
1341
- const file = join10(binDir, name);
1387
+ const file = join11(binDir, name);
1342
1388
  if (!existsSync3(file)) {
1343
1389
  missing.push(name);
1344
1390
  continue;
@@ -1380,12 +1426,25 @@ try {
1380
1426
  const s = status("codex", codexTranscript);
1381
1427
  const n = (x) => x.toLocaleString();
1382
1428
  console.log(`darkhunt endpoint status \u2014 codex`);
1429
+ const root = join12(dirname(fileURLToPath(import.meta.url)), "..", "..");
1430
+ let installed = "unknown";
1431
+ try {
1432
+ installed = JSON.parse(readFileSync7(join12(root, "package.json"), "utf8")).version;
1433
+ } catch {
1434
+ }
1435
+ console.log(` plugin ${installed} ${root}`);
1383
1436
  console.log(` profile ${s.profile} -> ${s.baseUrl}`);
1384
1437
  console.log(` tenant ${s.tenantId} workspace ${s.workspaceId}`);
1385
1438
  console.log(
1386
1439
  ` user ${s.userId ?? "(none \u2014 traces are unattributed)"}` + (s.userIdSource === "none" ? "" : ` [from ${s.userIdSource}]`)
1387
1440
  );
1388
1441
  console.log(` capture ${s.capture ? "ON" : "OFF"} enforce ${s.enforce}`);
1442
+ if (s.subagents && s.subagents.found > 0) {
1443
+ const { found, shipped } = s.subagents;
1444
+ console.log(
1445
+ ` subagents ${shipped} of ${found} shipped` + (shipped < found ? ` \u2014 ${found - shipped} not captured; run backfill.mjs` : "")
1446
+ );
1447
+ }
1389
1448
  const launchers = launcherState();
1390
1449
  const launcherProblems = launcherWarnings(launchers);
1391
1450
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkhunt-security/endpoint-codex",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "type": "module",
5
5
  "description": "Darkhunt endpoint adapter for Codex CLI: hook codec, rollout transcript mapper, plugin manifest.",
6
6
  "bin": {