@primitive.ai/prim 0.1.0-alpha.66 → 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.
@@ -9,7 +9,7 @@ import {
9
9
  HttpError,
10
10
  getClient,
11
11
  withFileLock
12
- } from "./chunk-CPX3BUOC.js";
12
+ } from "./chunk-RTLFE6EZ.js";
13
13
 
14
14
  // src/commands/skill.ts
15
15
  import { randomUUID } from "crypto";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getClient,
3
3
  getSiteUrl
4
- } from "./chunk-CPX3BUOC.js";
4
+ } from "./chunk-RTLFE6EZ.js";
5
5
  import {
6
6
  daemonRequest
7
7
  } from "./chunk-UTKQTZHL.js";
@@ -394,10 +394,12 @@ function refreshToken(options = {}) {
394
394
  }
395
395
  var HttpError = class extends Error {
396
396
  status;
397
- constructor(status, message) {
397
+ body;
398
+ constructor(status, message, body) {
398
399
  super(message);
399
400
  this.name = "HttpError";
400
401
  this.status = status;
402
+ this.body = body;
401
403
  }
402
404
  };
403
405
  var _cachedCredential;
@@ -458,7 +460,9 @@ async function request(method, path, body, options) {
458
460
  throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
459
461
  }
460
462
  const errorBody = await response.json().catch(() => null);
461
- throw new HttpError(response.status, errorBody?.error ?? `HTTP ${response.status}`);
463
+ const errorRecord = typeof errorBody === "object" && errorBody !== null && !Array.isArray(errorBody) ? errorBody : void 0;
464
+ const message = typeof errorRecord?.error === "string" ? errorRecord.error : `HTTP ${response.status}`;
465
+ throw new HttpError(response.status, message, errorBody);
462
466
  }
463
467
  return response.json();
464
468
  }
@@ -962,9 +966,12 @@ function validDecisionUrl(value) {
962
966
  return void 0;
963
967
  }
964
968
  }
965
- function decisionLink(label, decisionUrl) {
969
+ function decisionLink(label, decisionUrl, plain) {
966
970
  const url = validDecisionUrl(decisionUrl);
967
- 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`;
968
975
  }
969
976
  function formatLabeled(labels, cap) {
970
977
  if (labels === void 0) {
@@ -981,13 +988,13 @@ function formatLabeled(labels, cap) {
981
988
  function formatTeammates(names, cap) {
982
989
  return formatLabeled(names?.map(stripControlChars), cap);
983
990
  }
984
- function formatTeammatesWithArea(teammates, cap) {
991
+ function formatTeammatesWithArea(teammates, cap, plainLinks = false) {
985
992
  return formatLabeled(
986
993
  teammates?.map((t) => {
987
994
  const name = stripControlChars(t.name);
988
995
  const area = stripControlChars(t.area ?? "").trim();
989
996
  const label = area ? `${name} - ${area}` : name;
990
- return decisionLink(label, t.decisionUrl);
997
+ return decisionLink(label, t.decisionUrl, plainLinks);
991
998
  }),
992
999
  cap
993
1000
  );
@@ -995,25 +1002,26 @@ function formatTeammatesWithArea(teammates, cap) {
995
1002
 
996
1003
  // src/lib/statusline-render.ts
997
1004
  var STATUSLINE_NAME_CAP = 3;
998
- 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();
999
1008
  if (!snapshot) {
1000
- return `primitive ${version} (daemon: down)`;
1009
+ return `primitive ${version} (daemon: down${ingestionSuffix(ingestionStatus)})`;
1001
1010
  }
1002
1011
  if (snapshot.healthy === false) {
1003
1012
  if (snapshot.needsReauth) {
1004
- return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`)`;
1013
+ return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`${ingestionSuffix(ingestionStatus)})`;
1005
1014
  }
1006
1015
  if (snapshot.ingestion?.healthy === false) {
1007
1016
  const pending = snapshot.ingestion.pendingCount;
1008
1017
  const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
1009
- 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)})`;
1010
1019
  }
1011
1020
  if (snapshot.heartbeat?.healthy === false) {
1012
- return `primitive ${version} (daemon: degraded \xB7 presence: unavailable)`;
1021
+ return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${ingestionSuffix(ingestionStatus)})`;
1013
1022
  }
1014
- return `primitive ${version} (daemon: starting)`;
1023
+ return `primitive ${version} (daemon: starting${ingestionSuffix(ingestionStatus)})`;
1015
1024
  }
1016
- const ingestionStatus = resolveIngestionStatus();
1017
1025
  if (snapshot.envMismatch) {
1018
1026
  return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
1019
1027
  }
@@ -1022,7 +1030,7 @@ function formatStatusline(version, snapshot, resolveIngestionStatus) {
1022
1030
  }
1023
1031
  let team;
1024
1032
  if (snapshot.onlineTeammates !== void 0) {
1025
- team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP)}`;
1033
+ team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP, options.plainLinks === true)}`;
1026
1034
  } else if (snapshot.onlineNames !== void 0) {
1027
1035
  team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
1028
1036
  } else if (typeof snapshot.onlineCount === "number") {
@@ -1143,6 +1151,56 @@ function daemonRequest(method, params = {}, opts = {}) {
1143
1151
  });
1144
1152
  }
1145
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
+
1146
1204
  // src/daemon/env-binding.ts
1147
1205
  function normalizeApiUrl(url) {
1148
1206
  return url.trim().replace(/\/+$/, "");
@@ -1424,6 +1482,7 @@ var CONFIG_DIR = join9(homedir6(), ".config", "prim");
1424
1482
  var SOCK_PATH2 = join9(CONFIG_DIR, "sock");
1425
1483
  var HEARTBEAT_INTERVAL_MS = 3e4;
1426
1484
  var INGESTION_POLL_INTERVAL_MS = 15e3;
1485
+ var DECISION_DIGEST_POLL_INTERVAL_MS = 5e3;
1427
1486
  var TOKEN_CHECK_INTERVAL_MS = 6e4;
1428
1487
  var TOKEN_REFRESH_THRESHOLD_MS = 9e4;
1429
1488
  var HTTP_PROXY_TIMEOUT_MS = 1e4;
@@ -1435,6 +1494,11 @@ var client = getClient();
1435
1494
  var runtimeVersion = resolveRuntimeVersion();
1436
1495
  var daemonHealth = createDaemonHealthState(runtimeVersion, process.pid, startedAt);
1437
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
+ );
1438
1502
  var activeSessionId = process.env.PRIM_DAEMON_SESSION_ID ?? `daemon-${process.pid}`;
1439
1503
  var lastHeartbeatAt;
1440
1504
  var lastOnlineCount;
@@ -1443,6 +1507,7 @@ var lastOnlineTeammates;
1443
1507
  var lastOkAtLocal;
1444
1508
  var heartbeatTimer;
1445
1509
  var ingestionTimer;
1510
+ var decisionDigestTimer;
1446
1511
  var tokenCheckTimer;
1447
1512
  var heartbeatInFlight;
1448
1513
  var heartbeatRerunRequested = false;
@@ -1462,7 +1527,7 @@ function resolveRuntimeVersion() {
1462
1527
  return "unknown";
1463
1528
  }
1464
1529
  }
1465
- function errorMessage(err) {
1530
+ function errorMessage2(err) {
1466
1531
  return err instanceof Error ? err.message : String(err);
1467
1532
  }
1468
1533
  function persistHealth() {
@@ -1471,7 +1536,7 @@ function persistHealth() {
1471
1536
  try {
1472
1537
  writeDaemonHealthState(daemonHealth);
1473
1538
  } catch (err) {
1474
- process.stderr.write(`[prim-daemon] health-state error: ${errorMessage(err)}
1539
+ process.stderr.write(`[prim-daemon] health-state error: ${errorMessage2(err)}
1475
1540
  `);
1476
1541
  }
1477
1542
  }
@@ -1496,9 +1561,13 @@ function enterReauthHold() {
1496
1561
  clearTimeout(ingestionTimer);
1497
1562
  ingestionTimer = void 0;
1498
1563
  }
1564
+ if (decisionDigestTimer) {
1565
+ clearTimeout(decisionDigestTimer);
1566
+ decisionDigestTimer = void 0;
1567
+ }
1499
1568
  persistHealth();
1500
1569
  process.stderr.write(
1501
- "[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"
1502
1571
  );
1503
1572
  }
1504
1573
  function exitReauthHold() {
@@ -1513,10 +1582,11 @@ function exitReauthHold() {
1513
1582
  daemonHealth.ingestion.lastError = void 0;
1514
1583
  persistHealth();
1515
1584
  process.stderr.write(
1516
- "[prim-daemon] re-authentication detected \u2014 resuming heartbeat + ingestion\n"
1585
+ "[prim-daemon] re-authentication detected \u2014 resuming heartbeat + ingestion + Decision cache\n"
1517
1586
  );
1518
1587
  void sendHeartbeat();
1519
1588
  void runIngestionLoop();
1589
+ void runDecisionDigestLoop();
1520
1590
  }
1521
1591
  async function takeOwnership() {
1522
1592
  const now = Date.now();
@@ -1566,7 +1636,7 @@ function cleanup() {
1566
1636
  try {
1567
1637
  releaseDaemonOwnership(ownership, SOCK_PATH2);
1568
1638
  } catch (err) {
1569
- process.stderr.write(`[prim-daemon] cleanup error: ${errorMessage(err)}
1639
+ process.stderr.write(`[prim-daemon] cleanup error: ${errorMessage2(err)}
1570
1640
  `);
1571
1641
  }
1572
1642
  }
@@ -1599,14 +1669,14 @@ async function performHeartbeat() {
1599
1669
  }
1600
1670
  persistHealth();
1601
1671
  } catch (err) {
1602
- daemonHealth.heartbeat.lastError = errorMessage(err);
1672
+ daemonHealth.heartbeat.lastError = errorMessage2(err);
1603
1673
  daemonHealth.heartbeat.consecutiveFailures += 1;
1604
1674
  persistHealth();
1605
1675
  if (isSessionEnded()) {
1606
1676
  enterReauthHold();
1607
1677
  return;
1608
1678
  }
1609
- process.stderr.write(`[prim-daemon] heartbeat error: ${errorMessage(err)}
1679
+ process.stderr.write(`[prim-daemon] heartbeat error: ${errorMessage2(err)}
1610
1680
  `);
1611
1681
  }
1612
1682
  }
@@ -1677,7 +1747,7 @@ async function runIngestionLoop() {
1677
1747
  const failures = daemonHealth.ingestion.consecutiveFailures + 1;
1678
1748
  const delay = ingestionRetryDelayMs(failures);
1679
1749
  daemonHealth.ingestion.consecutiveFailures = failures;
1680
- daemonHealth.ingestion.lastError = `journal scan failed: ${errorMessage(err)}`;
1750
+ daemonHealth.ingestion.lastError = `journal scan failed: ${errorMessage2(err)}`;
1681
1751
  daemonHealth.ingestion.nextRetryAt = Date.now() + delay;
1682
1752
  persistHealth();
1683
1753
  scheduleIngestion(delay);
@@ -1712,7 +1782,7 @@ async function runIngestionLoop() {
1712
1782
  const delay = ingestionRetryDelayMs(failures);
1713
1783
  daemonHealth.ingestion.lastAcknowledgedCount = err instanceof FlushError ? err.flushed : 0;
1714
1784
  daemonHealth.ingestion.consecutiveFailures = failures;
1715
- daemonHealth.ingestion.lastError = errorMessage(err);
1785
+ daemonHealth.ingestion.lastError = errorMessage2(err);
1716
1786
  daemonHealth.ingestion.nextRetryAt = Date.now() + delay;
1717
1787
  try {
1718
1788
  updatePendingHealth();
@@ -1723,11 +1793,25 @@ async function runIngestionLoop() {
1723
1793
  enterReauthHold();
1724
1794
  return;
1725
1795
  }
1726
- process.stderr.write(`[prim-daemon] ingestion error: ${errorMessage(err)}
1796
+ process.stderr.write(`[prim-daemon] ingestion error: ${errorMessage2(err)}
1727
1797
  `);
1728
1798
  scheduleIngestion(delay);
1729
1799
  }
1730
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
+ }
1731
1815
  function pathParam(params) {
1732
1816
  if (typeof params.path !== "string" || !params.path.startsWith("/api/cli/")) {
1733
1817
  throw new Error("proxy request requires `path: string` under /api/cli/");
@@ -1814,6 +1898,7 @@ async function dispatchRequest(req) {
1814
1898
  activeSessionId = sid;
1815
1899
  }
1816
1900
  await sendHeartbeat();
1901
+ void refreshDecisionDigest();
1817
1902
  return { id, ok: true, result: { sessionId: activeSessionId } };
1818
1903
  }
1819
1904
  case "session_end": {
@@ -1821,6 +1906,12 @@ async function dispatchRequest(req) {
1821
1906
  }
1822
1907
  case "status_snapshot":
1823
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
+ }
1824
1915
  case "statusline_invalidate":
1825
1916
  statuslineIngestionCache.clear();
1826
1917
  return { id, ok: true, result: { ack: true } };
@@ -1955,6 +2046,7 @@ function startTimers() {
1955
2046
  persistHealth();
1956
2047
  void sendHeartbeat();
1957
2048
  void runIngestionLoop();
2049
+ void runDecisionDigestLoop();
1958
2050
  }
1959
2051
  void runTokenCheckLoop();
1960
2052
  }
@@ -1983,6 +2075,9 @@ function stopTimers() {
1983
2075
  if (ingestionTimer) {
1984
2076
  clearTimeout(ingestionTimer);
1985
2077
  }
2078
+ if (decisionDigestTimer) {
2079
+ clearTimeout(decisionDigestTimer);
2080
+ }
1986
2081
  if (tokenCheckTimer) {
1987
2082
  clearTimeout(tokenCheckTimer);
1988
2083
  }
@@ -1997,14 +2092,14 @@ function installSignalHandlers() {
1997
2092
  shutdown(EXIT_CRASH, `[prim-daemon] uncaught: ${err.message}`);
1998
2093
  });
1999
2094
  process.on("unhandledRejection", (err) => {
2000
- shutdown(EXIT_CRASH, `[prim-daemon] unhandled rejection: ${errorMessage(err)}`);
2095
+ shutdown(EXIT_CRASH, `[prim-daemon] unhandled rejection: ${errorMessage2(err)}`);
2001
2096
  });
2002
2097
  }
2003
2098
  async function main() {
2004
2099
  try {
2005
2100
  await takeOwnership();
2006
2101
  } catch (err) {
2007
- process.stderr.write(`[prim-daemon] ${errorMessage(err)}
2102
+ process.stderr.write(`[prim-daemon] ${errorMessage2(err)}
2008
2103
  `);
2009
2104
  cleanup();
2010
2105
  process.exit(EXIT_CRASH);
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  appendMove,
8
8
  resolveOrg
9
- } from "../chunk-HIMWM526.js";
9
+ } from "../chunk-UN5O5SYG.js";
10
10
  import {
11
11
  getOrCreateWorkspaceId
12
12
  } from "../chunk-UVIWZWM6.js";
@@ -16,7 +16,7 @@ import {
16
16
  repoSyncId,
17
17
  resolveRepositoryContext
18
18
  } from "../chunk-TDRXHBEX.js";
19
- import "../chunk-CPX3BUOC.js";
19
+ import "../chunk-RTLFE6EZ.js";
20
20
 
21
21
  // src/hooks/post-commit.ts
22
22
  import { execFileSync, spawn } from "child_process";
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  appendMove,
8
8
  resolveOrg
9
- } from "../chunk-HIMWM526.js";
9
+ } from "../chunk-UN5O5SYG.js";
10
10
  import {
11
11
  getOrCreateWorkspaceId
12
12
  } from "../chunk-UVIWZWM6.js";
@@ -16,7 +16,7 @@ import {
16
16
  repoSyncId,
17
17
  resolveRepositoryContext
18
18
  } from "../chunk-TDRXHBEX.js";
19
- import "../chunk-CPX3BUOC.js";
19
+ import "../chunk-RTLFE6EZ.js";
20
20
 
21
21
  // src/hooks/post-rewrite.ts
22
22
  import { execFileSync, spawn } from "child_process";
@@ -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
@@ -16,7 +14,7 @@ import {
16
14
  import {
17
15
  appendMove,
18
16
  resolveOrg
19
- } from "../chunk-HIMWM526.js";
17
+ } from "../chunk-UN5O5SYG.js";
20
18
  import {
21
19
  getOrCreateWorkspaceId
22
20
  } from "../chunk-UVIWZWM6.js";
@@ -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,
@@ -33,11 +37,12 @@ import {
33
37
  } from "../chunk-TDRXHBEX.js";
34
38
  import {
35
39
  getClient
36
- } from "../chunk-CPX3BUOC.js";
40
+ } from "../chunk-RTLFE6EZ.js";
37
41
  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
  });
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  checkAffectedDecisions,
4
4
  formatDecisionsWarning
5
- } from "../chunk-WS3MCNFD.js";
6
- import "../chunk-CPX3BUOC.js";
5
+ } from "../chunk-XUAP572Z.js";
6
+ import "../chunk-RTLFE6EZ.js";
7
7
  import "../chunk-UTKQTZHL.js";
8
8
 
9
9
  // src/hooks/pre-commit.ts