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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +39 -1
  2. package/SKILL.md +1 -1
  3. package/dist/{chunk-XPVNH3J6.js → chunk-2KW7DVFJ.js} +121 -130
  4. package/dist/chunk-3EEFL6NJ.js +529 -0
  5. package/dist/chunk-4A2XB2PC.js +310 -0
  6. package/dist/{chunk-G5D3FF3D.js → chunk-BQUJ4AFJ.js} +1 -83
  7. package/dist/chunk-CUE2LKLR.js +155 -0
  8. package/dist/chunk-FI3LIRCB.js +262 -0
  9. package/dist/{chunk-CPSE3Y3T.js → chunk-H64F6QHV.js} +27 -8
  10. package/dist/chunk-KKP2NYZ3.js +505 -0
  11. package/dist/{chunk-JQFN6LBO.js → chunk-LEMX3IOG.js} +6 -37
  12. package/dist/{chunk-U6L2RU67.js → chunk-LMRYZL3D.js} +52 -45
  13. package/dist/{chunk-XUAP572Z.js → chunk-RO3F6JYV.js} +8 -7
  14. package/dist/{chunk-TDRXHBEX.js → chunk-TJTVHDJ5.js} +41 -18
  15. package/dist/{chunk-TCDUH7AN.js → chunk-WB7AUZ4C.js} +17 -0
  16. package/dist/chunk-XGADQPCI.js +26 -0
  17. package/dist/{chunk-UN5O5SYG.js → chunk-YWO7O6QG.js} +14 -53
  18. package/dist/chunk-Z7YPFWAZ.js +917 -0
  19. package/dist/{chunk-BDGQEJA5.js → chunk-ZOSNEO5N.js} +2 -2
  20. package/dist/daemon/server.js +11804 -470
  21. package/dist/hook-runtime/daemon/server.js +13353 -0
  22. package/dist/hook-runtime/hooks/post-commit.js +1128 -0
  23. package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
  24. package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
  25. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  26. package/dist/hook-runtime/hooks/pre-tool-use.js +7099 -0
  27. package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
  28. package/dist/hook-runtime/hooks/session-end.js +495 -0
  29. package/dist/hook-runtime/hooks/session-start.js +11284 -0
  30. package/dist/hook-runtime/index.js +42775 -0
  31. package/dist/hook-runtime/statusline-main.js +765 -0
  32. package/dist/hooks/post-commit.js +7 -4
  33. package/dist/hooks/post-rewrite.js +7 -4
  34. package/dist/hooks/post-tool-use.js +64 -32
  35. package/dist/hooks/pre-commit.js +4 -3
  36. package/dist/hooks/pre-tool-use.js +118 -38
  37. package/dist/hooks/prim-hook.js +72 -27
  38. package/dist/hooks/session-end.js +5 -4
  39. package/dist/hooks/session-start.js +36 -25
  40. package/dist/index.js +15039 -2555
  41. package/dist/statusline-main.js +488 -96
  42. package/package.json +11 -5
  43. package/dist/chunk-CZ24W5QU.js +0 -123
  44. package/dist/chunk-RTLFE6EZ.js +0 -518
  45. package/dist/chunk-UTKQTZHL.js +0 -88
@@ -2,21 +2,24 @@
2
2
  import {
3
3
  commitAttributionFromEnvironment,
4
4
  toCommitMove
5
- } from "../chunk-CPSE3Y3T.js";
5
+ } from "../chunk-H64F6QHV.js";
6
6
  import {
7
7
  appendMove,
8
8
  resolveOrg
9
- } from "../chunk-UN5O5SYG.js";
9
+ } from "../chunk-YWO7O6QG.js";
10
10
  import {
11
11
  getOrCreateWorkspaceId
12
12
  } from "../chunk-UVIWZWM6.js";
13
+ import "../chunk-FI3LIRCB.js";
14
+ import "../chunk-LEMX3IOG.js";
13
15
  import {
14
16
  githubRepositoryFullName,
15
17
  isRepoActiveForCapture,
16
18
  repoSyncId,
17
19
  resolveRepositoryContext
18
- } from "../chunk-TDRXHBEX.js";
19
- import "../chunk-RTLFE6EZ.js";
20
+ } from "../chunk-TJTVHDJ5.js";
21
+ import "../chunk-Z7YPFWAZ.js";
22
+ import "../chunk-3EEFL6NJ.js";
20
23
 
21
24
  // src/hooks/post-commit.ts
22
25
  import { execFileSync, spawn } from "child_process";
@@ -2,21 +2,24 @@
2
2
  import {
3
3
  canonicalRewritePairs,
4
4
  toRewriteMove
5
- } from "../chunk-CPSE3Y3T.js";
5
+ } from "../chunk-H64F6QHV.js";
6
6
  import {
7
7
  appendMove,
8
8
  resolveOrg
9
- } from "../chunk-UN5O5SYG.js";
9
+ } from "../chunk-YWO7O6QG.js";
10
10
  import {
11
11
  getOrCreateWorkspaceId
12
12
  } from "../chunk-UVIWZWM6.js";
13
+ import "../chunk-FI3LIRCB.js";
14
+ import "../chunk-LEMX3IOG.js";
13
15
  import {
14
16
  githubRepositoryFullName,
15
17
  isRepoActiveForCapture,
16
18
  repoSyncId,
17
19
  resolveRepositoryContext
18
- } from "../chunk-TDRXHBEX.js";
19
- import "../chunk-RTLFE6EZ.js";
20
+ } from "../chunk-TJTVHDJ5.js";
21
+ import "../chunk-Z7YPFWAZ.js";
22
+ import "../chunk-3EEFL6NJ.js";
20
23
 
21
24
  // src/hooks/post-rewrite.ts
22
25
  import { execFileSync, spawn } from "child_process";
@@ -1,43 +1,51 @@
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
10
- } from "../chunk-BDGQEJA5.js";
8
+ } from "../chunk-ZOSNEO5N.js";
11
9
  import {
12
10
  postToolInvocationId,
13
11
  toMove,
14
12
  toolOutcomeFor
15
- } from "../chunk-CPSE3Y3T.js";
13
+ } from "../chunk-H64F6QHV.js";
16
14
  import {
17
15
  appendMove,
18
16
  resolveOrg
19
- } from "../chunk-UN5O5SYG.js";
17
+ } from "../chunk-YWO7O6QG.js";
20
18
  import {
21
19
  getOrCreateWorkspaceId
22
20
  } from "../chunk-UVIWZWM6.js";
21
+ import "../chunk-BQUJ4AFJ.js";
22
+ import {
23
+ appendCodexContext,
24
+ prepareCodexContext
25
+ } from "../chunk-KKP2NYZ3.js";
26
+ import "../chunk-4A2XB2PC.js";
23
27
  import {
24
28
  scrubFromCwd
25
- } from "../chunk-G5D3FF3D.js";
29
+ } from "../chunk-FI3LIRCB.js";
26
30
  import {
27
- warmBinCache
28
- } from "../chunk-CZ24W5QU.js";
31
+ bold,
32
+ color
33
+ } from "../chunk-LEMX3IOG.js";
29
34
  import {
30
35
  isRepoActiveForCapture,
31
36
  repoSyncId,
32
37
  resolveRepositoryContext
33
- } from "../chunk-TDRXHBEX.js";
38
+ } from "../chunk-TJTVHDJ5.js";
34
39
  import {
35
- getClient
36
- } from "../chunk-RTLFE6EZ.js";
40
+ getClient,
41
+ terminalSafeLine
42
+ } from "../chunk-Z7YPFWAZ.js";
37
43
  import {
38
44
  normalizeEnvelope,
39
45
  parseAgent
40
- } from "../chunk-TCDUH7AN.js";
46
+ } from "../chunk-WB7AUZ4C.js";
47
+ import "../chunk-CUE2LKLR.js";
48
+ import "../chunk-3EEFL6NJ.js";
41
49
 
42
50
  // src/hooks/post-tool-use.ts
43
51
  import { readFileSync } from "fs";
@@ -61,7 +69,7 @@ function renderVerdictFooter(ctx) {
61
69
  const successPrefix = color("\u2713 Conflict caught before merge", "green");
62
70
  const savedCount = `${String(ctx.decisionsSaved)}${ctx.decisionsSavedTruncated ? "+" : ""}`;
63
71
  const savedFragment = `${savedCount} decisions saved`;
64
- const intentFragment = `${bold(ctx.author)}'s intent preserved`;
72
+ const intentFragment = `${bold(terminalSafeLine(ctx.author))}'s intent preserved`;
65
73
  return `${successPrefix} \xB7 ${savedFragment} \xB7 ${intentFragment}`;
66
74
  }
67
75
  function isVerdictFooterContext(value) {
@@ -112,8 +120,19 @@ function readStdin() {
112
120
  });
113
121
  });
114
122
  }
115
- function emit() {
116
- process.stdout.write("{}\n");
123
+ async function emit(output = {}) {
124
+ return await new Promise((resolve) => {
125
+ try {
126
+ process.stdout.write(`${JSON.stringify(output)}
127
+ `, (error) => resolve(!error));
128
+ } catch {
129
+ resolve(false);
130
+ }
131
+ });
132
+ }
133
+ async function emitWithAcknowledgment(output, acknowledge) {
134
+ const handedOff = await emit(output);
135
+ await acknowledge?.(handedOff);
117
136
  }
118
137
  function debug(msg) {
119
138
  if (process.env.PRIM_HOOK_VERBOSE === "1") {
@@ -122,37 +141,36 @@ function debug(msg) {
122
141
  }
123
142
  }
124
143
  async function main() {
125
- warmBinCache();
126
144
  const agent = parseAgent(process.argv);
127
145
  let raw;
128
146
  try {
129
147
  raw = await readStdin();
130
148
  } catch {
131
- emit();
149
+ await emit();
132
150
  return;
133
151
  }
134
152
  let parsed;
135
153
  try {
136
154
  parsed = normalizeEnvelope(JSON.parse(raw), agent);
137
155
  } catch {
138
- emit();
156
+ await emit();
139
157
  return;
140
158
  }
141
159
  let envelope = parsed;
142
160
  const isToolResult = envelope.hook_event_name === "PostToolUse" || agent === "claude_code" && envelope.hook_event_name === "PostToolUseFailure";
143
161
  const isHermesDenial = agent === "hermes" && envelope.hook_event_name === "post_approval_response" && toolOutcomeFor(parsed, agent) === "prevented";
144
162
  if (!isToolResult && !isHermesDenial) {
145
- emit();
163
+ await emit();
146
164
  return;
147
165
  }
148
166
  const invocationId = postToolInvocationId(parsed, agent);
149
167
  if (isHermesDenial && !invocationId) {
150
- emit();
168
+ await emit();
151
169
  return;
152
170
  }
153
171
  const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
154
172
  if (!isHermesDenial && !editingToolsFor(agent).has(toolName)) {
155
- emit();
173
+ await emit();
156
174
  return;
157
175
  }
158
176
  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 +178,17 @@ async function main() {
160
178
  envelope = parsed;
161
179
  }
162
180
  if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
163
- emit();
181
+ await emit();
164
182
  return;
165
183
  }
166
184
  const cwd = parsed.cwd ?? process.cwd();
167
185
  if (!isRepoActiveForCapture(cwd)) {
168
- emit();
186
+ await emit();
169
187
  return;
170
188
  }
171
189
  const resolvedRepository = resolveRepositoryContext(cwd);
172
190
  if (!resolvedRepository) {
173
- emit();
191
+ await emit();
174
192
  return;
175
193
  }
176
194
  const repository = { ...resolvedRepository, repoSyncId: repoSyncId(cwd) };
@@ -183,11 +201,11 @@ async function main() {
183
201
  const { resolution } = enrichment;
184
202
  if (!isHermesDenial) {
185
203
  if (resolution.shellMutation === "none") {
186
- emit();
204
+ await emit();
187
205
  return;
188
206
  }
189
207
  if (resolution.shellMutation === void 0 && resolution.fileRefs.length === 0) {
190
- emit();
208
+ await emit();
191
209
  return;
192
210
  }
193
211
  }
@@ -195,24 +213,38 @@ async function main() {
195
213
  const identity = getOrCreateWorkspaceId(cwd);
196
214
  const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
197
215
  const base = toMove(enriched, resolveCliVersion(), agent, workspaceId, repository, invocationId);
198
- const scrubbed = scrubFromCwd(enriched, cwd);
216
+ const scrubbed = await scrubFromCwd(enriched, cwd);
199
217
  const move = {
200
218
  ...base,
201
219
  payload: preserveHookFileMetadata(scrubbed, resolution)
202
220
  };
203
- const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
221
+ const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd });
222
+ let verdictFooter = false;
204
223
  try {
205
224
  const result = await deliverPostToolMove(move, orgId);
206
225
  debug(`durably ingested ${move.moveId} (${toolName})`);
207
226
  if (isVerdictFooterContext(result.verdictFooter)) {
227
+ verdictFooter = true;
208
228
  process.stderr.write(`${renderVerdictFooter(result.verdictFooter)}
209
229
  `);
210
230
  }
211
231
  } catch (err) {
212
232
  debug(`ingest failed: ${err instanceof Error ? err.message : String(err)}`);
213
233
  }
214
- emit();
234
+ if (agent === "codex" && verdictFooter) {
235
+ try {
236
+ const context = await prepareCodexContext({
237
+ cwd,
238
+ sessionId: envelope.session_id,
239
+ includeDigest: false
240
+ });
241
+ await emitWithAcknowledgment(appendCodexContext({}, context.context), context.acknowledge);
242
+ return;
243
+ } catch {
244
+ }
245
+ }
246
+ await emit();
215
247
  }
216
- main().catch(() => {
217
- emit();
248
+ main().catch(async () => {
249
+ await emit();
218
250
  });
@@ -2,9 +2,10 @@
2
2
  import {
3
3
  checkAffectedDecisions,
4
4
  formatDecisionsWarning
5
- } from "../chunk-XUAP572Z.js";
6
- import "../chunk-RTLFE6EZ.js";
7
- import "../chunk-UTKQTZHL.js";
5
+ } from "../chunk-RO3F6JYV.js";
6
+ import "../chunk-Z7YPFWAZ.js";
7
+ import "../chunk-CUE2LKLR.js";
8
+ import "../chunk-3EEFL6NJ.js";
8
9
 
9
10
  // src/hooks/pre-commit.ts
10
11
  import { execSync } from "child_process";
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- DEFAULT_RULES,
4
3
  analyzeShellTargets,
5
4
  buildCodexOutput,
6
5
  buildHermesOutput,
@@ -10,26 +9,38 @@ import {
10
9
  failOpenCodex,
11
10
  failOpenHermes,
12
11
  failOpenOutput,
13
- readHookMode,
12
+ readHookMode
13
+ } from "../chunk-BQUJ4AFJ.js";
14
+ import {
15
+ appendCodexContext,
16
+ hasVisibleCodexMessage,
17
+ prepareCodexContext
18
+ } from "../chunk-KKP2NYZ3.js";
19
+ import {
20
+ packageVersion
21
+ } from "../chunk-4A2XB2PC.js";
22
+ import {
23
+ DEFAULT_RULES,
14
24
  scrub
15
- } from "../chunk-G5D3FF3D.js";
25
+ } from "../chunk-FI3LIRCB.js";
16
26
  import {
17
- packageVersion,
18
- warmBinCache
19
- } from "../chunk-CZ24W5QU.js";
27
+ boundedHealthError
28
+ } from "../chunk-LEMX3IOG.js";
20
29
  import {
21
30
  canonicalGitRoot,
22
31
  canonicalRepositoryPath,
23
32
  isRepoActive,
24
33
  repoSyncId
25
- } from "../chunk-TDRXHBEX.js";
34
+ } from "../chunk-TJTVHDJ5.js";
26
35
  import {
27
36
  getClient
28
- } from "../chunk-RTLFE6EZ.js";
37
+ } from "../chunk-Z7YPFWAZ.js";
29
38
  import {
30
39
  normalizeEnvelope,
31
40
  parseAgent
32
- } from "../chunk-TCDUH7AN.js";
41
+ } from "../chunk-WB7AUZ4C.js";
42
+ import "../chunk-CUE2LKLR.js";
43
+ import "../chunk-3EEFL6NJ.js";
33
44
 
34
45
  // src/hooks/preflight-v3.ts
35
46
  import { Buffer as Buffer2 } from "buffer";
@@ -39,6 +50,9 @@ var MAX_PREFLIGHT_PATHS = 32;
39
50
  var MAX_PROPOSAL_BYTES = 6144;
40
51
  var MAX_CLIENT_VERSION_CHARS = 32;
41
52
  var CLIENT_VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
53
+ var MAX_DECISION_DISCLOSURES = 16;
54
+ var DISCLOSED_DECISION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
55
+ var DISCLOSED_DECISION_SHORT_ID_RE = /^[0-9a-f]{8}$/u;
42
56
  function boundedClientVersion(raw) {
43
57
  return typeof raw === "string" && raw.length <= MAX_CLIENT_VERSION_CHARS && CLIENT_VERSION_RE.test(raw) ? raw : "unknown";
44
58
  }
@@ -88,8 +102,26 @@ function parsePreflightResponse(value) {
88
102
  if (response.protocolVersion !== PREFLIGHT_PROTOCOL_VERSION || typeof response.verdict !== "string" || !verdicts.has(response.verdict) || typeof response.reasonCode !== "string" || typeof response.message !== "string" || !Array.isArray(response.conflicts) || !Array.isArray(response.bypassed)) {
89
103
  return null;
90
104
  }
105
+ const disclosures = response.decisionDisclosures;
106
+ if (disclosures !== void 0) {
107
+ if (!Array.isArray(disclosures) || disclosures.length === 0 || disclosures.length > MAX_DECISION_DISCLOSURES) {
108
+ return null;
109
+ }
110
+ const seen = /* @__PURE__ */ new Set();
111
+ for (const value2 of disclosures) {
112
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
113
+ const disclosure = value2;
114
+ if (Object.keys(disclosure).length !== 3 || !Object.hasOwn(disclosure, "decisionId") || !Object.hasOwn(disclosure, "shortId") || !Object.hasOwn(disclosure, "participation") || typeof disclosure.decisionId !== "string" || !DISCLOSED_DECISION_ID_RE.test(disclosure.decisionId) || typeof disclosure.shortId !== "string" || !DISCLOSED_DECISION_SHORT_ID_RE.test(disclosure.shortId) || disclosure.participation !== "candidate" && disclosure.participation !== "reconcile_bypass" || seen.has(disclosure.decisionId)) {
115
+ return null;
116
+ }
117
+ seen.add(disclosure.decisionId);
118
+ }
119
+ }
91
120
  return response;
92
121
  }
122
+ function decisionDisclosureContext(response) {
123
+ return response.decisionDisclosures?.map(({ decisionId }) => `[primitive] hidden Decision: prim decisions show ${decisionId}`).join("\n") ?? "";
124
+ }
93
125
  async function requestPreflight(request, signal = AbortSignal.timeout(PREFLIGHT_TIMEOUT_MS)) {
94
126
  const response = await getClient().post("/api/cli/decisions/conflict-check", request, {
95
127
  signal,
@@ -99,6 +131,8 @@ async function requestPreflight(request, signal = AbortSignal.timeout(PREFLIGHT_
99
131
  }
100
132
  function resultForPreflight(response) {
101
133
  const verdict = response.verdict === "block" ? "deny" : response.verdict;
134
+ const message = boundedHealthError(response.message) ?? "";
135
+ const unavailable = message || boundedHealthError(response.reasonCode);
102
136
  const safe = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
103
137
  const directives = response.conflicts.flatMap((value) => {
104
138
  if (!value || typeof value !== "object") return [];
@@ -106,14 +140,15 @@ function resultForPreflight(response) {
106
140
  const id = typeof shortId === "string" && safe.test(shortId) ? `dec_${shortId}` : typeof decisionId === "string" && safe.test(decisionId) ? decisionId : void 0;
107
141
  return id ? [`To reconcile, run: prim reconcile ${id}`] : [];
108
142
  });
109
- const reason = [response.message, ...new Set(directives)].filter(Boolean).join("\n");
143
+ const reason = [message, ...new Set(directives)].filter(Boolean).join("\n");
144
+ const disclosureContext = decisionDisclosureContext(response);
110
145
  return {
111
146
  verdict,
112
147
  conflicts: response.conflicts,
113
148
  reason,
114
- additionalContext: ["warn", "ask", "block"].includes(response.verdict) ? reason : "",
149
+ additionalContext: disclosureContext || (["warn", "ask", "block"].includes(response.verdict) ? reason : ""),
115
150
  truncated: false,
116
- unavailable: response.verdict === "unavailable" ? response.message || response.reasonCode : void 0
151
+ unavailable: response.verdict === "unavailable" ? unavailable : void 0
117
152
  };
118
153
  }
119
154
  function unverifiedResult(message) {
@@ -147,9 +182,19 @@ async function readStdin() {
147
182
  });
148
183
  }
149
184
  var agent = parseAgent(process.argv);
150
- function emit(output) {
151
- process.stdout.write(`${JSON.stringify(output)}
152
- `);
185
+ async function emit(output) {
186
+ return await new Promise((resolve) => {
187
+ try {
188
+ process.stdout.write(`${JSON.stringify(output)}
189
+ `, (error) => resolve(!error));
190
+ } catch {
191
+ resolve(false);
192
+ }
193
+ });
194
+ }
195
+ async function emitWithAcknowledgment(output, acknowledge) {
196
+ const handedOff = await emit(output);
197
+ await acknowledge?.(handedOff);
153
198
  }
154
199
  function failOpen() {
155
200
  if (agent === "hermes") return failOpenHermes();
@@ -159,25 +204,48 @@ function invocationId(envelope) {
159
204
  const value = agent === "hermes" ? envelope.extra?.tool_call_id : envelope.tool_use_id;
160
205
  return typeof value === "string" && value.length > 0 ? value : void 0;
161
206
  }
162
- function emitUnverified(message) {
207
+ function hermesAdvisory(result, aggregate) {
208
+ const parts = [];
209
+ if (aggregate === "warn") parts.push(result.reason);
210
+ if (result.verdict === "unavailable") {
211
+ parts.push(result.unavailable ?? result.reason);
212
+ }
213
+ if (result.additionalContext && result.additionalContext !== result.reason) {
214
+ parts.push(result.additionalContext);
215
+ }
216
+ const message = [...new Set(parts.filter(Boolean))].join("\n");
217
+ return message.startsWith("[primitive]") ? message : message ? `[primitive] ${message}` : "";
218
+ }
219
+ async function emitUnverified(message, envelope) {
163
220
  const result = unverifiedResult(message);
164
221
  if (agent === "hermes") {
165
222
  process.stderr.write(`[primitive] ${message}
166
223
  `);
167
- emit(failOpenHermes());
224
+ await emit(failOpenHermes());
168
225
  } else {
169
- emit(
170
- agent === "codex" ? buildCodexOutput("allow", [result]) : buildHookOutput("allow", [result])
171
- );
226
+ let output = agent === "codex" ? buildCodexOutput("allow", [result]) : buildHookOutput("allow", [result]);
227
+ if (agent === "codex" && typeof envelope?.session_id === "string" && envelope.session_id) {
228
+ try {
229
+ const context = await prepareCodexContext({
230
+ cwd: envelope.cwd ?? process.cwd(),
231
+ sessionId: envelope.session_id,
232
+ includeDigest: false
233
+ });
234
+ output = appendCodexContext(output, context.context);
235
+ await emitWithAcknowledgment(output, context.acknowledge);
236
+ return;
237
+ } catch {
238
+ }
239
+ }
240
+ await emit(output);
172
241
  }
173
242
  }
174
243
  async function main() {
175
- warmBinCache();
176
244
  let raw;
177
245
  try {
178
246
  raw = await readStdin();
179
247
  } catch {
180
- emit(failOpen());
248
+ await emit(failOpen());
181
249
  return;
182
250
  }
183
251
  let envelope;
@@ -187,17 +255,17 @@ async function main() {
187
255
  agent
188
256
  );
189
257
  } catch {
190
- emit(failOpen());
258
+ await emit(failOpen());
191
259
  return;
192
260
  }
193
261
  if (envelope.hook_event_name !== "PreToolUse") {
194
- emit(failOpen());
262
+ await emit(failOpen());
195
263
  return;
196
264
  }
197
265
  const env = process.env;
198
266
  const mode = readHookMode(env);
199
267
  if (mode === "off") {
200
- emit(failOpen());
268
+ await emit(failOpen());
201
269
  return;
202
270
  }
203
271
  const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
@@ -209,22 +277,25 @@ async function main() {
209
277
  cwd
210
278
  });
211
279
  if (targets.mutation === "none") {
212
- emit(failOpen());
280
+ await emit(failOpen());
213
281
  return;
214
282
  }
215
283
  if (!isRepoActive(cwd)) {
216
- emit(failOpen());
284
+ await emit(failOpen());
217
285
  return;
218
286
  }
219
287
  if (targets.paths.length === 0) {
220
- emitUnverified("mutation targets could not be determined; enforcement not verified");
288
+ await emitUnverified(
289
+ "mutation targets could not be determined; enforcement not verified",
290
+ envelope
291
+ );
221
292
  return;
222
293
  }
223
294
  const binding = repoSyncId(cwd);
224
295
  const sessionId = envelope.session_id;
225
296
  const callId = invocationId(envelope);
226
297
  if (!binding || typeof sessionId !== "string" || !sessionId || !callId) {
227
- emitUnverified("repository binding or tool invocation identity is unavailable");
298
+ await emitUnverified("repository binding or tool invocation identity is unavailable", envelope);
228
299
  return;
229
300
  }
230
301
  const request = {
@@ -243,26 +314,35 @@ async function main() {
243
314
  try {
244
315
  const response = await requestPreflight(request);
245
316
  if (!response) {
246
- emitUnverified("enforcement service returned an incompatible response");
317
+ await emitUnverified("enforcement service returned an incompatible response", envelope);
247
318
  return;
248
319
  }
249
320
  result = resultForPreflight(response);
250
321
  } catch {
251
- emitUnverified("enforcement service unavailable; change was not verified");
322
+ await emitUnverified("enforcement service unavailable; change was not verified", envelope);
252
323
  return;
253
324
  }
254
325
  const aggregate = demoteForMode(
255
326
  result.verdict === "unavailable" ? "allow" : result.verdict,
256
327
  mode
257
328
  );
258
- if (agent === "hermes" && (aggregate === "warn" || result.verdict === "unavailable")) {
259
- process.stderr.write(`[primitive] ${result.reason || result.unavailable}
329
+ if (agent === "hermes" && aggregate !== "deny" && aggregate !== "ask") {
330
+ const advisory = hermesAdvisory(result, aggregate);
331
+ if (advisory) process.stderr.write(`${advisory}
260
332
  `);
261
333
  }
262
- emit(
263
- agent === "hermes" ? buildHermesOutput(aggregate, [result]) : agent === "codex" ? buildCodexOutput(aggregate, [result]) : buildHookOutput(aggregate, [result])
264
- );
334
+ let output = agent === "hermes" ? buildHermesOutput(aggregate, [result]) : agent === "codex" ? buildCodexOutput(aggregate, [result]) : buildHookOutput(aggregate, [result]);
335
+ if (agent === "codex" && hasVisibleCodexMessage(output) && typeof sessionId === "string" && sessionId.length > 0) {
336
+ try {
337
+ const context = await prepareCodexContext({ cwd, sessionId, includeDigest: false });
338
+ output = appendCodexContext(output, context.context);
339
+ await emitWithAcknowledgment(output, context.acknowledge);
340
+ return;
341
+ } catch {
342
+ }
343
+ }
344
+ await emit(output);
265
345
  }
266
- main().catch(() => {
267
- emit(failOpen());
346
+ main().catch(async () => {
347
+ await emit(failOpen());
268
348
  });