@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.
@@ -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,
@@ -25,11 +30,12 @@ import {
25
30
  } from "../chunk-TDRXHBEX.js";
26
31
  import {
27
32
  getClient
28
- } from "../chunk-CPX3BUOC.js";
33
+ } from "../chunk-RTLFE6EZ.js";
29
34
  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
  });
@@ -8,7 +8,7 @@ import {
8
8
  acknowledgeDecisionFeedback,
9
9
  leaseDecisionFeedback,
10
10
  renderFeedback
11
- } from "../chunk-KHZNYEUV.js";
11
+ } from "../chunk-U6L2RU67.js";
12
12
  import {
13
13
  enrichHookPayloadWithFileRefs,
14
14
  preserveHookFileMetadata
@@ -21,32 +21,88 @@ import {
21
21
  import {
22
22
  appendMove,
23
23
  resolveOrg
24
- } from "../chunk-HIMWM526.js";
24
+ } from "../chunk-UN5O5SYG.js";
25
25
  import {
26
26
  getOrCreateWorkspaceId
27
27
  } from "../chunk-UVIWZWM6.js";
28
28
  import {
29
29
  scrubFromCwd
30
30
  } from "../chunk-G5D3FF3D.js";
31
+ import {
32
+ prepareCodexContext
33
+ } from "../chunk-I5BHJQH2.js";
31
34
  import {
32
35
  warmBinCache
33
- } from "../chunk-CZ24W5QU.js";
36
+ } from "../chunk-K3AYW6VZ.js";
34
37
  import {
35
38
  isRepoActiveForCapture,
36
39
  repoSyncId,
37
40
  resolveRepositoryContext
38
41
  } from "../chunk-TDRXHBEX.js";
39
- import "../chunk-CPX3BUOC.js";
42
+ import "../chunk-RTLFE6EZ.js";
40
43
  import {
41
44
  normalizeEnvelope,
42
45
  parseAgent
43
46
  } from "../chunk-TCDUH7AN.js";
47
+ import "../chunk-UTKQTZHL.js";
44
48
 
45
49
  // src/hooks/prim-hook.ts
46
50
  import { spawn } from "child_process";
47
51
  import { readFileSync } from "fs";
48
52
  import { dirname, join } from "path";
49
53
  import { fileURLToPath } from "url";
54
+
55
+ // src/hooks/codex-message-context.ts
56
+ var defaultDeps = { prepare: prepareCodexContext };
57
+ var STOP_BACKSTOP_INSTRUCTION = "A new Primitive Decision arrived during this turn. Incorporate it into the active task and proactively tell the user about it before finishing.";
58
+ async function processCodexMessageContext(envelope, deps = defaultDeps) {
59
+ const event = envelope.hook_event_name;
60
+ if (event !== "UserPromptSubmit" && event !== "Stop") return { output: {} };
61
+ if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
62
+ return { output: {} };
63
+ }
64
+ if (event === "Stop" && envelope.stop_hook_active === true) return { output: {} };
65
+ let prepared;
66
+ try {
67
+ prepared = await deps.prepare({
68
+ cwd: typeof envelope.cwd === "string" ? envelope.cwd : process.cwd(),
69
+ sessionId: envelope.session_id
70
+ });
71
+ } catch {
72
+ return { output: {} };
73
+ }
74
+ if (event === "UserPromptSubmit") {
75
+ return {
76
+ output: prepared.context ? {
77
+ hookSpecificOutput: {
78
+ hookEventName: "UserPromptSubmit",
79
+ additionalContext: prepared.context
80
+ }
81
+ } : {},
82
+ // Even an empty prompt response is a successful handoff. Committing it
83
+ // advances verified feed state and report dedup without inventing output.
84
+ acknowledge: async () => {
85
+ await prepared.acknowledge(true);
86
+ }
87
+ };
88
+ }
89
+ if (!prepared.decisionDigest) {
90
+ return { output: {} };
91
+ }
92
+ return {
93
+ output: {
94
+ decision: "block",
95
+ reason: `${prepared.context ?? prepared.decisionDigest}
96
+
97
+ ${STOP_BACKSTOP_INSTRUCTION}`
98
+ },
99
+ acknowledge: async () => {
100
+ await prepared.acknowledge(true);
101
+ }
102
+ };
103
+ }
104
+
105
+ // src/hooks/prim-hook.ts
50
106
  var here = dirname(fileURLToPath(import.meta.url));
51
107
  var outputAttempted = false;
52
108
  function emitOutput(output, acknowledge) {
@@ -90,8 +146,9 @@ async function main() {
90
146
  }
91
147
  const cwd = parsed.cwd ?? process.cwd();
92
148
  const isClaudeStop = agent === "claude_code" && parsed.hook_event_name === "Stop";
149
+ const isCodexContextEvent = agent === "codex" && (parsed.hook_event_name === "UserPromptSubmit" || parsed.hook_event_name === "Stop");
93
150
  if (!isRepoActiveForCapture(cwd)) {
94
- if (isClaudeStop) await emitOutput(buildHookOutput({}));
151
+ if (isClaudeStop || isCodexContextEvent) await emitOutput(buildHookOutput({}));
95
152
  return;
96
153
  }
97
154
  const identity = getOrCreateWorkspaceId(cwd);
@@ -123,6 +180,11 @@ async function main() {
123
180
  } catch (error) {
124
181
  debug("capture", error);
125
182
  }
183
+ if (isCodexContextEvent) {
184
+ const result = await processCodexMessageContext(parsed);
185
+ await emitOutput(result.output, result.acknowledge);
186
+ return;
187
+ }
126
188
  if (!isClaudeStop) return;
127
189
  const sessionId = typeof parsed.session_id === "string" ? parsed.session_id : "";
128
190
  if (!workspaceId || !sessionId) {
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- daemonRequest
4
- } from "../chunk-UTKQTZHL.js";
5
2
  import {
6
3
  normalizeEnvelope,
7
4
  parseAgent
8
5
  } from "../chunk-TCDUH7AN.js";
6
+ import {
7
+ daemonRequest
8
+ } from "../chunk-UTKQTZHL.js";
9
9
 
10
10
  // src/hooks/session-end.ts
11
11
  var STDIN_TIMEOUT_MS = 1e3;
@@ -5,7 +5,7 @@ import {
5
5
  ensureEffectivePostRewriteHook,
6
6
  hasUsableCodexGuidance,
7
7
  refreshClaudePlugins
8
- } from "../chunk-DR66XM5L.js";
8
+ } from "../chunk-XPVNH3J6.js";
9
9
  import {
10
10
  buildHookOutput,
11
11
  handoffHookOutput
@@ -15,14 +15,17 @@ import {
15
15
  acknowledgeDecisionFeedback,
16
16
  leaseDecisionFeedback,
17
17
  renderFeedback
18
- } from "../chunk-KHZNYEUV.js";
18
+ } from "../chunk-U6L2RU67.js";
19
19
  import {
20
20
  getOrCreateWorkspaceId
21
21
  } from "../chunk-UVIWZWM6.js";
22
+ import {
23
+ prepareCodexContext
24
+ } from "../chunk-I5BHJQH2.js";
22
25
  import {
23
26
  binFile,
24
27
  warmBinCache
25
- } from "../chunk-CZ24W5QU.js";
28
+ } from "../chunk-K3AYW6VZ.js";
26
29
  import {
27
30
  gitToplevel,
28
31
  isRepoActiveForCapture,
@@ -30,16 +33,15 @@ import {
30
33
  setRepoActive
31
34
  } from "../chunk-TDRXHBEX.js";
32
35
  import {
33
- getSiteUrl,
34
36
  isSessionEnded
35
- } from "../chunk-CPX3BUOC.js";
36
- import {
37
- daemonRequest
38
- } from "../chunk-UTKQTZHL.js";
37
+ } from "../chunk-RTLFE6EZ.js";
39
38
  import {
40
39
  normalizeEnvelope,
41
40
  parseAgent
42
41
  } from "../chunk-TCDUH7AN.js";
42
+ import {
43
+ daemonRequest
44
+ } from "../chunk-UTKQTZHL.js";
43
45
 
44
46
  // src/daemon/self-heal.ts
45
47
  import { spawn } from "child_process";
@@ -68,7 +70,6 @@ function kickDaemonEnsure(options = {}) {
68
70
  // src/hooks/reauth-notice.ts
69
71
  var REAUTH_NOTICE = "[prim] authentication ended \u2014 run `prim auth login` to resume decision capture";
70
72
  function reauthNoticeFields(agent) {
71
- if (agent === "codex") return { additionalContext: REAUTH_NOTICE };
72
73
  if (agent === "claude_code") return { systemMessage: REAUTH_NOTICE };
73
74
  return;
74
75
  }
@@ -156,7 +157,7 @@ async function processSessionStart(raw, agent, feedbackSignal) {
156
157
  } catch {
157
158
  }
158
159
  }
159
- if (isSessionEnded()) {
160
+ if (isSessionEnded() && agent !== "codex") {
160
161
  const notice = reauthNoticeFields(agent);
161
162
  if (notice) {
162
163
  return {
@@ -165,14 +166,16 @@ async function processSessionStart(raw, agent, feedbackSignal) {
165
166
  }
166
167
  }
167
168
  if (agent === "codex") {
168
- const snapshot = await daemonRequest(
169
- "status_snapshot",
170
- // callerEnv: a cross-env daemon withholds onlineCount, so a prod Codex
171
- // session never gets a staging daemon's team count injected.
172
- { callerEnv: getSiteUrl() },
173
- { timeoutMs: DAEMON_TIMEOUT_MS }
174
- );
175
- const presence = snapshot && !snapshot.presenceStale && typeof snapshot.onlineCount === "number" ? `[prim] team: ${snapshot.onlineCount} online` : void 0;
169
+ let context;
170
+ try {
171
+ context = await prepareCodexContext({
172
+ cwd,
173
+ sessionId: envelope.session_id,
174
+ startup: true,
175
+ includeDigest: false
176
+ });
177
+ } catch {
178
+ }
176
179
  projectRoot = await activeProjectRoot(cwd);
177
180
  active = projectRoot !== null;
178
181
  let proactive = false;
@@ -180,8 +183,13 @@ async function processSessionStart(raw, agent, feedbackSignal) {
180
183
  proactive = projectRoot !== null && active && hasUsableCodexGuidance(projectRoot);
181
184
  } catch {
182
185
  }
183
- const additionalContext = [proactive ? CODEX_PRIM_REMINDER : void 0, presence].filter((value) => value !== void 0).join("\n\n");
184
- return { output: buildHookOutput({ additionalContext }) };
186
+ const additionalContext = [proactive ? CODEX_PRIM_REMINDER : void 0, context?.context].filter((value) => value !== void 0).join("\n\n");
187
+ return {
188
+ output: buildHookOutput({ additionalContext }),
189
+ acknowledge: async () => {
190
+ await context?.acknowledge(true);
191
+ }
192
+ };
185
193
  }
186
194
  if (agent === "claude_code") {
187
195
  const additionalContext = active && skillState.installed > 0 ? PRIM_SKILL_REMINDER : void 0;