@primitive.ai/prim 0.1.0-alpha.67 → 0.1.0-alpha.68

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.
@@ -966,9 +966,12 @@ function validDecisionUrl(value) {
966
966
  return void 0;
967
967
  }
968
968
  }
969
- function decisionLink(label, decisionUrl) {
969
+ function decisionLink(label, decisionUrl, plain) {
970
970
  const url = validDecisionUrl(decisionUrl);
971
- return url ? `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07` : label;
971
+ if (plain || !url) {
972
+ return label;
973
+ }
974
+ return `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07`;
972
975
  }
973
976
  function formatLabeled(labels, cap) {
974
977
  if (labels === void 0) {
@@ -985,13 +988,13 @@ function formatLabeled(labels, cap) {
985
988
  function formatTeammates(names, cap) {
986
989
  return formatLabeled(names?.map(stripControlChars), cap);
987
990
  }
988
- function formatTeammatesWithArea(teammates, cap) {
991
+ function formatTeammatesWithArea(teammates, cap, plainLinks = false) {
989
992
  return formatLabeled(
990
993
  teammates?.map((t) => {
991
994
  const name = stripControlChars(t.name);
992
995
  const area = stripControlChars(t.area ?? "").trim();
993
996
  const label = area ? `${name} - ${area}` : name;
994
- return decisionLink(label, t.decisionUrl);
997
+ return decisionLink(label, t.decisionUrl, plainLinks);
995
998
  }),
996
999
  cap
997
1000
  );
@@ -999,25 +1002,26 @@ function formatTeammatesWithArea(teammates, cap) {
999
1002
 
1000
1003
  // src/lib/statusline-render.ts
1001
1004
  var STATUSLINE_NAME_CAP = 3;
1002
- function formatStatusline(version, snapshot, resolveIngestionStatus) {
1005
+ function formatStatusline(version, snapshot, resolveIngestionStatus, options = {}) {
1006
+ const ingestionSuffix = (status) => status === void 0 ? "" : ` \xB7 Decision ingestion ${status}`;
1007
+ const ingestionStatus = snapshot === null || snapshot.healthy === false ? options.includeIngestionWhenUnavailable ? resolveIngestionStatus() : void 0 : resolveIngestionStatus();
1003
1008
  if (!snapshot) {
1004
- return `primitive ${version} (daemon: down)`;
1009
+ return `primitive ${version} (daemon: down${ingestionSuffix(ingestionStatus)})`;
1005
1010
  }
1006
1011
  if (snapshot.healthy === false) {
1007
1012
  if (snapshot.needsReauth) {
1008
- return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`)`;
1013
+ return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`${ingestionSuffix(ingestionStatus)})`;
1009
1014
  }
1010
1015
  if (snapshot.ingestion?.healthy === false) {
1011
1016
  const pending = snapshot.ingestion.pendingCount;
1012
1017
  const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
1013
- return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""})`;
1018
+ return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${ingestionSuffix(ingestionStatus)})`;
1014
1019
  }
1015
1020
  if (snapshot.heartbeat?.healthy === false) {
1016
- return `primitive ${version} (daemon: degraded \xB7 presence: unavailable)`;
1021
+ return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${ingestionSuffix(ingestionStatus)})`;
1017
1022
  }
1018
- return `primitive ${version} (daemon: starting)`;
1023
+ return `primitive ${version} (daemon: starting${ingestionSuffix(ingestionStatus)})`;
1019
1024
  }
1020
- const ingestionStatus = resolveIngestionStatus();
1021
1025
  if (snapshot.envMismatch) {
1022
1026
  return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
1023
1027
  }
@@ -1026,7 +1030,7 @@ function formatStatusline(version, snapshot, resolveIngestionStatus) {
1026
1030
  }
1027
1031
  let team;
1028
1032
  if (snapshot.onlineTeammates !== void 0) {
1029
- team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP)}`;
1033
+ team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP, options.plainLinks === true)}`;
1030
1034
  } else if (snapshot.onlineNames !== void 0) {
1031
1035
  team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
1032
1036
  } else if (typeof snapshot.onlineCount === "number") {
@@ -1147,6 +1151,56 @@ function daemonRequest(method, params = {}, opts = {}) {
1147
1151
  });
1148
1152
  }
1149
1153
 
1154
+ // src/daemon/decision-digest-cache.ts
1155
+ var DECISION_DIGEST_CACHE_WARMING = "daemon Decision cache warming";
1156
+ var DECISION_DIGEST_CACHE_LIMIT = 100;
1157
+ var DECISION_DIGEST_CACHE_WINDOW = "24h";
1158
+ var DECISION_DIGEST_CACHE_PATH = `/api/cli/decisions/recent?limit=${String(DECISION_DIGEST_CACHE_LIMIT)}&since=${DECISION_DIGEST_CACHE_WINDOW}`;
1159
+ function errorMessage(error) {
1160
+ return error instanceof Error ? error.message : String(error);
1161
+ }
1162
+ var DecisionDigestCache = class {
1163
+ constructor(load, now = Date.now) {
1164
+ this.load = load;
1165
+ this.now = now;
1166
+ }
1167
+ snapshot = {
1168
+ decisions: [],
1169
+ unavailable: DECISION_DIGEST_CACHE_WARMING
1170
+ };
1171
+ refreshInFlight;
1172
+ read() {
1173
+ return { ...this.snapshot, decisions: [...this.snapshot.decisions] };
1174
+ }
1175
+ refresh() {
1176
+ if (this.refreshInFlight) return this.refreshInFlight;
1177
+ this.refreshInFlight = this.performRefresh().finally(() => {
1178
+ this.refreshInFlight = void 0;
1179
+ });
1180
+ return this.refreshInFlight;
1181
+ }
1182
+ async performRefresh() {
1183
+ try {
1184
+ const value = await this.load();
1185
+ if (typeof value !== "object" || value === null || !Array.isArray(value.decisions)) {
1186
+ throw new Error("malformed Decision feed response");
1187
+ }
1188
+ this.snapshot = {
1189
+ decisions: value.decisions,
1190
+ cachedAt: this.now(),
1191
+ unavailable: typeof value.unavailable === "string" ? value.unavailable : void 0
1192
+ };
1193
+ } catch (error) {
1194
+ if (this.snapshot.cachedAt === void 0) {
1195
+ this.snapshot = {
1196
+ decisions: [],
1197
+ unavailable: `daemon Decision cache unavailable: ${errorMessage(error)}`
1198
+ };
1199
+ }
1200
+ }
1201
+ }
1202
+ };
1203
+
1150
1204
  // src/daemon/env-binding.ts
1151
1205
  function normalizeApiUrl(url) {
1152
1206
  return url.trim().replace(/\/+$/, "");
@@ -1428,6 +1482,7 @@ var CONFIG_DIR = join9(homedir6(), ".config", "prim");
1428
1482
  var SOCK_PATH2 = join9(CONFIG_DIR, "sock");
1429
1483
  var HEARTBEAT_INTERVAL_MS = 3e4;
1430
1484
  var INGESTION_POLL_INTERVAL_MS = 15e3;
1485
+ var DECISION_DIGEST_POLL_INTERVAL_MS = 5e3;
1431
1486
  var TOKEN_CHECK_INTERVAL_MS = 6e4;
1432
1487
  var TOKEN_REFRESH_THRESHOLD_MS = 9e4;
1433
1488
  var HTTP_PROXY_TIMEOUT_MS = 1e4;
@@ -1439,6 +1494,11 @@ var client = getClient();
1439
1494
  var runtimeVersion = resolveRuntimeVersion();
1440
1495
  var daemonHealth = createDaemonHealthState(runtimeVersion, process.pid, startedAt);
1441
1496
  var statuslineIngestionCache = new StatuslineIngestionCache(decisionIngestionStatus);
1497
+ var decisionDigestCache = new DecisionDigestCache(
1498
+ async () => await client.get(DECISION_DIGEST_CACHE_PATH, {
1499
+ signal: AbortSignal.timeout(HTTP_PROXY_TIMEOUT_MS)
1500
+ })
1501
+ );
1442
1502
  var activeSessionId = process.env.PRIM_DAEMON_SESSION_ID ?? `daemon-${process.pid}`;
1443
1503
  var lastHeartbeatAt;
1444
1504
  var lastOnlineCount;
@@ -1447,6 +1507,7 @@ var lastOnlineTeammates;
1447
1507
  var lastOkAtLocal;
1448
1508
  var heartbeatTimer;
1449
1509
  var ingestionTimer;
1510
+ var decisionDigestTimer;
1450
1511
  var tokenCheckTimer;
1451
1512
  var heartbeatInFlight;
1452
1513
  var heartbeatRerunRequested = false;
@@ -1466,7 +1527,7 @@ function resolveRuntimeVersion() {
1466
1527
  return "unknown";
1467
1528
  }
1468
1529
  }
1469
- function errorMessage(err) {
1530
+ function errorMessage2(err) {
1470
1531
  return err instanceof Error ? err.message : String(err);
1471
1532
  }
1472
1533
  function persistHealth() {
@@ -1475,7 +1536,7 @@ function persistHealth() {
1475
1536
  try {
1476
1537
  writeDaemonHealthState(daemonHealth);
1477
1538
  } catch (err) {
1478
- process.stderr.write(`[prim-daemon] health-state error: ${errorMessage(err)}
1539
+ process.stderr.write(`[prim-daemon] health-state error: ${errorMessage2(err)}
1479
1540
  `);
1480
1541
  }
1481
1542
  }
@@ -1500,9 +1561,13 @@ function enterReauthHold() {
1500
1561
  clearTimeout(ingestionTimer);
1501
1562
  ingestionTimer = void 0;
1502
1563
  }
1564
+ if (decisionDigestTimer) {
1565
+ clearTimeout(decisionDigestTimer);
1566
+ decisionDigestTimer = void 0;
1567
+ }
1503
1568
  persistHealth();
1504
1569
  process.stderr.write(
1505
- "[prim-daemon] authentication ended \u2014 halting heartbeat + ingestion until `prim auth login` (captures continue to the journal)\n"
1570
+ "[prim-daemon] authentication ended \u2014 halting heartbeat + ingestion + Decision cache until `prim auth login` (captures continue to the journal)\n"
1506
1571
  );
1507
1572
  }
1508
1573
  function exitReauthHold() {
@@ -1517,10 +1582,11 @@ function exitReauthHold() {
1517
1582
  daemonHealth.ingestion.lastError = void 0;
1518
1583
  persistHealth();
1519
1584
  process.stderr.write(
1520
- "[prim-daemon] re-authentication detected \u2014 resuming heartbeat + ingestion\n"
1585
+ "[prim-daemon] re-authentication detected \u2014 resuming heartbeat + ingestion + Decision cache\n"
1521
1586
  );
1522
1587
  void sendHeartbeat();
1523
1588
  void runIngestionLoop();
1589
+ void runDecisionDigestLoop();
1524
1590
  }
1525
1591
  async function takeOwnership() {
1526
1592
  const now = Date.now();
@@ -1570,7 +1636,7 @@ function cleanup() {
1570
1636
  try {
1571
1637
  releaseDaemonOwnership(ownership, SOCK_PATH2);
1572
1638
  } catch (err) {
1573
- process.stderr.write(`[prim-daemon] cleanup error: ${errorMessage(err)}
1639
+ process.stderr.write(`[prim-daemon] cleanup error: ${errorMessage2(err)}
1574
1640
  `);
1575
1641
  }
1576
1642
  }
@@ -1603,14 +1669,14 @@ async function performHeartbeat() {
1603
1669
  }
1604
1670
  persistHealth();
1605
1671
  } catch (err) {
1606
- daemonHealth.heartbeat.lastError = errorMessage(err);
1672
+ daemonHealth.heartbeat.lastError = errorMessage2(err);
1607
1673
  daemonHealth.heartbeat.consecutiveFailures += 1;
1608
1674
  persistHealth();
1609
1675
  if (isSessionEnded()) {
1610
1676
  enterReauthHold();
1611
1677
  return;
1612
1678
  }
1613
- process.stderr.write(`[prim-daemon] heartbeat error: ${errorMessage(err)}
1679
+ process.stderr.write(`[prim-daemon] heartbeat error: ${errorMessage2(err)}
1614
1680
  `);
1615
1681
  }
1616
1682
  }
@@ -1681,7 +1747,7 @@ async function runIngestionLoop() {
1681
1747
  const failures = daemonHealth.ingestion.consecutiveFailures + 1;
1682
1748
  const delay = ingestionRetryDelayMs(failures);
1683
1749
  daemonHealth.ingestion.consecutiveFailures = failures;
1684
- daemonHealth.ingestion.lastError = `journal scan failed: ${errorMessage(err)}`;
1750
+ daemonHealth.ingestion.lastError = `journal scan failed: ${errorMessage2(err)}`;
1685
1751
  daemonHealth.ingestion.nextRetryAt = Date.now() + delay;
1686
1752
  persistHealth();
1687
1753
  scheduleIngestion(delay);
@@ -1716,7 +1782,7 @@ async function runIngestionLoop() {
1716
1782
  const delay = ingestionRetryDelayMs(failures);
1717
1783
  daemonHealth.ingestion.lastAcknowledgedCount = err instanceof FlushError ? err.flushed : 0;
1718
1784
  daemonHealth.ingestion.consecutiveFailures = failures;
1719
- daemonHealth.ingestion.lastError = errorMessage(err);
1785
+ daemonHealth.ingestion.lastError = errorMessage2(err);
1720
1786
  daemonHealth.ingestion.nextRetryAt = Date.now() + delay;
1721
1787
  try {
1722
1788
  updatePendingHealth();
@@ -1727,11 +1793,25 @@ async function runIngestionLoop() {
1727
1793
  enterReauthHold();
1728
1794
  return;
1729
1795
  }
1730
- process.stderr.write(`[prim-daemon] ingestion error: ${errorMessage(err)}
1796
+ process.stderr.write(`[prim-daemon] ingestion error: ${errorMessage2(err)}
1731
1797
  `);
1732
1798
  scheduleIngestion(delay);
1733
1799
  }
1734
1800
  }
1801
+ function scheduleDecisionDigestRefresh() {
1802
+ if (shuttingDown || reauthHold) return;
1803
+ decisionDigestTimer = setTimeout(() => {
1804
+ decisionDigestTimer = void 0;
1805
+ void runDecisionDigestLoop();
1806
+ }, DECISION_DIGEST_POLL_INTERVAL_MS);
1807
+ }
1808
+ function refreshDecisionDigest() {
1809
+ return shuttingDown || reauthHold ? Promise.resolve() : decisionDigestCache.refresh();
1810
+ }
1811
+ async function runDecisionDigestLoop() {
1812
+ await refreshDecisionDigest();
1813
+ scheduleDecisionDigestRefresh();
1814
+ }
1735
1815
  function pathParam(params) {
1736
1816
  if (typeof params.path !== "string" || !params.path.startsWith("/api/cli/")) {
1737
1817
  throw new Error("proxy request requires `path: string` under /api/cli/");
@@ -1818,6 +1898,7 @@ async function dispatchRequest(req) {
1818
1898
  activeSessionId = sid;
1819
1899
  }
1820
1900
  await sendHeartbeat();
1901
+ void refreshDecisionDigest();
1821
1902
  return { id, ok: true, result: { sessionId: activeSessionId } };
1822
1903
  }
1823
1904
  case "session_end": {
@@ -1825,6 +1906,12 @@ async function dispatchRequest(req) {
1825
1906
  }
1826
1907
  case "status_snapshot":
1827
1908
  return { id, ok: true, result: handleStatusSnapshot(req.params ?? {}) };
1909
+ case "decision_digest_snapshot": {
1910
+ assertCallerEnvMatches(req.params?.callerEnv, getSiteUrl());
1911
+ const result = decisionDigestCache.read();
1912
+ void refreshDecisionDigest();
1913
+ return { id, ok: true, result };
1914
+ }
1828
1915
  case "statusline_invalidate":
1829
1916
  statuslineIngestionCache.clear();
1830
1917
  return { id, ok: true, result: { ack: true } };
@@ -1959,6 +2046,7 @@ function startTimers() {
1959
2046
  persistHealth();
1960
2047
  void sendHeartbeat();
1961
2048
  void runIngestionLoop();
2049
+ void runDecisionDigestLoop();
1962
2050
  }
1963
2051
  void runTokenCheckLoop();
1964
2052
  }
@@ -1987,6 +2075,9 @@ function stopTimers() {
1987
2075
  if (ingestionTimer) {
1988
2076
  clearTimeout(ingestionTimer);
1989
2077
  }
2078
+ if (decisionDigestTimer) {
2079
+ clearTimeout(decisionDigestTimer);
2080
+ }
1990
2081
  if (tokenCheckTimer) {
1991
2082
  clearTimeout(tokenCheckTimer);
1992
2083
  }
@@ -2001,14 +2092,14 @@ function installSignalHandlers() {
2001
2092
  shutdown(EXIT_CRASH, `[prim-daemon] uncaught: ${err.message}`);
2002
2093
  });
2003
2094
  process.on("unhandledRejection", (err) => {
2004
- shutdown(EXIT_CRASH, `[prim-daemon] unhandled rejection: ${errorMessage(err)}`);
2095
+ shutdown(EXIT_CRASH, `[prim-daemon] unhandled rejection: ${errorMessage2(err)}`);
2005
2096
  });
2006
2097
  }
2007
2098
  async function main() {
2008
2099
  try {
2009
2100
  await takeOwnership();
2010
2101
  } catch (err) {
2011
- process.stderr.write(`[prim-daemon] ${errorMessage(err)}
2102
+ process.stderr.write(`[prim-daemon] ${errorMessage2(err)}
2012
2103
  `);
2013
2104
  cleanup();
2014
2105
  process.exit(EXIT_CRASH);
@@ -1,9 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- bold,
4
- color,
5
3
  requireDurableIngestAcknowledgement
6
- } from "../chunk-JQFN6LBO.js";
4
+ } from "../chunk-XGADQPCI.js";
7
5
  import {
8
6
  enrichHookPayloadWithFileRefs,
9
7
  preserveHookFileMetadata
@@ -24,8 +22,14 @@ import {
24
22
  scrubFromCwd
25
23
  } from "../chunk-G5D3FF3D.js";
26
24
  import {
25
+ appendCodexContext,
26
+ prepareCodexContext
27
+ } from "../chunk-I5BHJQH2.js";
28
+ import {
29
+ bold,
30
+ color,
27
31
  warmBinCache
28
- } from "../chunk-CZ24W5QU.js";
32
+ } from "../chunk-K3AYW6VZ.js";
29
33
  import {
30
34
  isRepoActiveForCapture,
31
35
  repoSyncId,
@@ -38,6 +42,7 @@ import {
38
42
  normalizeEnvelope,
39
43
  parseAgent
40
44
  } from "../chunk-TCDUH7AN.js";
45
+ import "../chunk-UTKQTZHL.js";
41
46
 
42
47
  // src/hooks/post-tool-use.ts
43
48
  import { readFileSync } from "fs";
@@ -112,8 +117,19 @@ function readStdin() {
112
117
  });
113
118
  });
114
119
  }
115
- function emit() {
116
- process.stdout.write("{}\n");
120
+ async function emit(output = {}) {
121
+ return await new Promise((resolve) => {
122
+ try {
123
+ process.stdout.write(`${JSON.stringify(output)}
124
+ `, (error) => resolve(!error));
125
+ } catch {
126
+ resolve(false);
127
+ }
128
+ });
129
+ }
130
+ async function emitWithAcknowledgment(output, acknowledge) {
131
+ const handedOff = await emit(output);
132
+ await acknowledge?.(handedOff);
117
133
  }
118
134
  function debug(msg) {
119
135
  if (process.env.PRIM_HOOK_VERBOSE === "1") {
@@ -128,31 +144,31 @@ async function main() {
128
144
  try {
129
145
  raw = await readStdin();
130
146
  } catch {
131
- emit();
147
+ await emit();
132
148
  return;
133
149
  }
134
150
  let parsed;
135
151
  try {
136
152
  parsed = normalizeEnvelope(JSON.parse(raw), agent);
137
153
  } catch {
138
- emit();
154
+ await emit();
139
155
  return;
140
156
  }
141
157
  let envelope = parsed;
142
158
  const isToolResult = envelope.hook_event_name === "PostToolUse" || agent === "claude_code" && envelope.hook_event_name === "PostToolUseFailure";
143
159
  const isHermesDenial = agent === "hermes" && envelope.hook_event_name === "post_approval_response" && toolOutcomeFor(parsed, agent) === "prevented";
144
160
  if (!isToolResult && !isHermesDenial) {
145
- emit();
161
+ await emit();
146
162
  return;
147
163
  }
148
164
  const invocationId = postToolInvocationId(parsed, agent);
149
165
  if (isHermesDenial && !invocationId) {
150
- emit();
166
+ await emit();
151
167
  return;
152
168
  }
153
169
  const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
154
170
  if (!isHermesDenial && !editingToolsFor(agent).has(toolName)) {
155
- emit();
171
+ await emit();
156
172
  return;
157
173
  }
158
174
  if (isHermesDenial && (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) && typeof envelope.extra?.session_key === "string" && envelope.extra.session_key.length > 0) {
@@ -160,17 +176,17 @@ async function main() {
160
176
  envelope = parsed;
161
177
  }
162
178
  if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
163
- emit();
179
+ await emit();
164
180
  return;
165
181
  }
166
182
  const cwd = parsed.cwd ?? process.cwd();
167
183
  if (!isRepoActiveForCapture(cwd)) {
168
- emit();
184
+ await emit();
169
185
  return;
170
186
  }
171
187
  const resolvedRepository = resolveRepositoryContext(cwd);
172
188
  if (!resolvedRepository) {
173
- emit();
189
+ await emit();
174
190
  return;
175
191
  }
176
192
  const repository = { ...resolvedRepository, repoSyncId: repoSyncId(cwd) };
@@ -183,11 +199,11 @@ async function main() {
183
199
  const { resolution } = enrichment;
184
200
  if (!isHermesDenial) {
185
201
  if (resolution.shellMutation === "none") {
186
- emit();
202
+ await emit();
187
203
  return;
188
204
  }
189
205
  if (resolution.shellMutation === void 0 && resolution.fileRefs.length === 0) {
190
- emit();
206
+ await emit();
191
207
  return;
192
208
  }
193
209
  }
@@ -201,18 +217,32 @@ async function main() {
201
217
  payload: preserveHookFileMetadata(scrubbed, resolution)
202
218
  };
203
219
  const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
220
+ let verdictFooter = false;
204
221
  try {
205
222
  const result = await deliverPostToolMove(move, orgId);
206
223
  debug(`durably ingested ${move.moveId} (${toolName})`);
207
224
  if (isVerdictFooterContext(result.verdictFooter)) {
225
+ verdictFooter = true;
208
226
  process.stderr.write(`${renderVerdictFooter(result.verdictFooter)}
209
227
  `);
210
228
  }
211
229
  } catch (err) {
212
230
  debug(`ingest failed: ${err instanceof Error ? err.message : String(err)}`);
213
231
  }
214
- emit();
232
+ if (agent === "codex" && verdictFooter) {
233
+ try {
234
+ const context = await prepareCodexContext({
235
+ cwd,
236
+ sessionId: envelope.session_id,
237
+ includeDigest: false
238
+ });
239
+ await emitWithAcknowledgment(appendCodexContext({}, context.context), context.acknowledge);
240
+ return;
241
+ } catch {
242
+ }
243
+ }
244
+ await emit();
215
245
  }
216
- main().catch(() => {
217
- emit();
246
+ main().catch(async () => {
247
+ await emit();
218
248
  });
@@ -13,10 +13,15 @@ import {
13
13
  readHookMode,
14
14
  scrub
15
15
  } from "../chunk-G5D3FF3D.js";
16
+ import {
17
+ appendCodexContext,
18
+ hasVisibleCodexMessage,
19
+ prepareCodexContext
20
+ } from "../chunk-I5BHJQH2.js";
16
21
  import {
17
22
  packageVersion,
18
23
  warmBinCache
19
- } from "../chunk-CZ24W5QU.js";
24
+ } from "../chunk-K3AYW6VZ.js";
20
25
  import {
21
26
  canonicalGitRoot,
22
27
  canonicalRepositoryPath,
@@ -30,6 +35,7 @@ import {
30
35
  normalizeEnvelope,
31
36
  parseAgent
32
37
  } from "../chunk-TCDUH7AN.js";
38
+ import "../chunk-UTKQTZHL.js";
33
39
 
34
40
  // src/hooks/preflight-v3.ts
35
41
  import { Buffer as Buffer2 } from "buffer";
@@ -147,9 +153,19 @@ async function readStdin() {
147
153
  });
148
154
  }
149
155
  var agent = parseAgent(process.argv);
150
- function emit(output) {
151
- process.stdout.write(`${JSON.stringify(output)}
152
- `);
156
+ async function emit(output) {
157
+ return await new Promise((resolve) => {
158
+ try {
159
+ process.stdout.write(`${JSON.stringify(output)}
160
+ `, (error) => resolve(!error));
161
+ } catch {
162
+ resolve(false);
163
+ }
164
+ });
165
+ }
166
+ async function emitWithAcknowledgment(output, acknowledge) {
167
+ const handedOff = await emit(output);
168
+ await acknowledge?.(handedOff);
153
169
  }
154
170
  function failOpen() {
155
171
  if (agent === "hermes") return failOpenHermes();
@@ -159,16 +175,28 @@ function invocationId(envelope) {
159
175
  const value = agent === "hermes" ? envelope.extra?.tool_call_id : envelope.tool_use_id;
160
176
  return typeof value === "string" && value.length > 0 ? value : void 0;
161
177
  }
162
- function emitUnverified(message) {
178
+ async function emitUnverified(message, envelope) {
163
179
  const result = unverifiedResult(message);
164
180
  if (agent === "hermes") {
165
181
  process.stderr.write(`[primitive] ${message}
166
182
  `);
167
- emit(failOpenHermes());
183
+ await emit(failOpenHermes());
168
184
  } else {
169
- emit(
170
- agent === "codex" ? buildCodexOutput("allow", [result]) : buildHookOutput("allow", [result])
171
- );
185
+ let output = agent === "codex" ? buildCodexOutput("allow", [result]) : buildHookOutput("allow", [result]);
186
+ if (agent === "codex" && typeof envelope?.session_id === "string" && envelope.session_id) {
187
+ try {
188
+ const context = await prepareCodexContext({
189
+ cwd: envelope.cwd ?? process.cwd(),
190
+ sessionId: envelope.session_id,
191
+ includeDigest: false
192
+ });
193
+ output = appendCodexContext(output, context.context);
194
+ await emitWithAcknowledgment(output, context.acknowledge);
195
+ return;
196
+ } catch {
197
+ }
198
+ }
199
+ await emit(output);
172
200
  }
173
201
  }
174
202
  async function main() {
@@ -177,7 +205,7 @@ async function main() {
177
205
  try {
178
206
  raw = await readStdin();
179
207
  } catch {
180
- emit(failOpen());
208
+ await emit(failOpen());
181
209
  return;
182
210
  }
183
211
  let envelope;
@@ -187,17 +215,17 @@ async function main() {
187
215
  agent
188
216
  );
189
217
  } catch {
190
- emit(failOpen());
218
+ await emit(failOpen());
191
219
  return;
192
220
  }
193
221
  if (envelope.hook_event_name !== "PreToolUse") {
194
- emit(failOpen());
222
+ await emit(failOpen());
195
223
  return;
196
224
  }
197
225
  const env = process.env;
198
226
  const mode = readHookMode(env);
199
227
  if (mode === "off") {
200
- emit(failOpen());
228
+ await emit(failOpen());
201
229
  return;
202
230
  }
203
231
  const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
@@ -209,22 +237,25 @@ async function main() {
209
237
  cwd
210
238
  });
211
239
  if (targets.mutation === "none") {
212
- emit(failOpen());
240
+ await emit(failOpen());
213
241
  return;
214
242
  }
215
243
  if (!isRepoActive(cwd)) {
216
- emit(failOpen());
244
+ await emit(failOpen());
217
245
  return;
218
246
  }
219
247
  if (targets.paths.length === 0) {
220
- emitUnverified("mutation targets could not be determined; enforcement not verified");
248
+ await emitUnverified(
249
+ "mutation targets could not be determined; enforcement not verified",
250
+ envelope
251
+ );
221
252
  return;
222
253
  }
223
254
  const binding = repoSyncId(cwd);
224
255
  const sessionId = envelope.session_id;
225
256
  const callId = invocationId(envelope);
226
257
  if (!binding || typeof sessionId !== "string" || !sessionId || !callId) {
227
- emitUnverified("repository binding or tool invocation identity is unavailable");
258
+ await emitUnverified("repository binding or tool invocation identity is unavailable", envelope);
228
259
  return;
229
260
  }
230
261
  const request = {
@@ -243,12 +274,12 @@ async function main() {
243
274
  try {
244
275
  const response = await requestPreflight(request);
245
276
  if (!response) {
246
- emitUnverified("enforcement service returned an incompatible response");
277
+ await emitUnverified("enforcement service returned an incompatible response", envelope);
247
278
  return;
248
279
  }
249
280
  result = resultForPreflight(response);
250
281
  } catch {
251
- emitUnverified("enforcement service unavailable; change was not verified");
282
+ await emitUnverified("enforcement service unavailable; change was not verified", envelope);
252
283
  return;
253
284
  }
254
285
  const aggregate = demoteForMode(
@@ -259,10 +290,18 @@ async function main() {
259
290
  process.stderr.write(`[primitive] ${result.reason || result.unavailable}
260
291
  `);
261
292
  }
262
- emit(
263
- agent === "hermes" ? buildHermesOutput(aggregate, [result]) : agent === "codex" ? buildCodexOutput(aggregate, [result]) : buildHookOutput(aggregate, [result])
264
- );
293
+ let output = agent === "hermes" ? buildHermesOutput(aggregate, [result]) : agent === "codex" ? buildCodexOutput(aggregate, [result]) : buildHookOutput(aggregate, [result]);
294
+ if (agent === "codex" && hasVisibleCodexMessage(output) && typeof sessionId === "string" && sessionId.length > 0) {
295
+ try {
296
+ const context = await prepareCodexContext({ cwd, sessionId, includeDigest: false });
297
+ output = appendCodexContext(output, context.context);
298
+ await emitWithAcknowledgment(output, context.acknowledge);
299
+ return;
300
+ } catch {
301
+ }
302
+ }
303
+ await emit(output);
265
304
  }
266
- main().catch(() => {
267
- emit(failOpen());
305
+ main().catch(async () => {
306
+ await emit(failOpen());
268
307
  });