@primitive.ai/prim 0.1.0-alpha.63 → 0.1.0-alpha.64

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.
@@ -15,15 +15,61 @@ var AGENT_ENVELOPE_VERSION = 3;
15
15
  var ENVELOPE_VERSION = LEGACY_ENVELOPE_VERSION;
16
16
 
17
17
  // src/hooks/prim-hook-core.ts
18
+ var CORRELATED_TOOL_EVENTS = /* @__PURE__ */ new Set([
19
+ "PostToolUse",
20
+ "PostToolUseFailure",
21
+ "post_tool_call",
22
+ "post_approval_response"
23
+ ]);
18
24
  function postToolInvocationId(parsed, agent) {
19
- if (parsed.hook_event_name !== "PostToolUse") return void 0;
25
+ if (typeof parsed.hook_event_name !== "string" || !CORRELATED_TOOL_EVENTS.has(parsed.hook_event_name)) {
26
+ return void 0;
27
+ }
20
28
  const raw = agent === "hermes" ? parsed.extra?.tool_call_id : parsed.tool_use_id;
21
29
  return typeof raw === "string" && raw.length > 0 ? raw : void 0;
22
30
  }
31
+ function lowerString(value) {
32
+ return typeof value === "string" ? value.toLowerCase() : void 0;
33
+ }
34
+ function toolOutcomeFor(parsed, agent) {
35
+ const event = parsed.hook_event_name;
36
+ if (agent === "claude_code") {
37
+ if (event === "PostToolUseFailure") {
38
+ return parsed.is_interrupt === true ? "interrupted" : "failed";
39
+ }
40
+ return event === "PostToolUse" ? "succeeded" : void 0;
41
+ }
42
+ if (agent === "codex") {
43
+ return event === "PostToolUse" ? "returned" : void 0;
44
+ }
45
+ const extra = parsed.extra;
46
+ if (event === "post_approval_response") {
47
+ const choice = lowerString(extra?.choice);
48
+ return (choice === "deny" || choice === "smart_deny" || choice === "timeout") && postToolInvocationId(parsed, agent) ? "prevented" : void 0;
49
+ }
50
+ if (event !== "PostToolUse" && event !== "post_tool_call") {
51
+ return void 0;
52
+ }
53
+ const status = lowerString(extra?.status);
54
+ if (status === "ok" || status === "success") {
55
+ return "succeeded";
56
+ }
57
+ if (status === "error" || status === "timeout") {
58
+ return "failed";
59
+ }
60
+ if (status === "cancelled") {
61
+ return "interrupted";
62
+ }
63
+ if (status === "blocked") {
64
+ return "prevented";
65
+ }
66
+ return "unknown";
67
+ }
23
68
  function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, repository, invocationId) {
24
69
  const sessionId = parsed.session_id ?? "";
25
70
  const eventType = parsed.hook_event_name ?? "unknown";
26
- const moveId = eventType === "PostToolUse" && sessionId && invocationId ? `posttool:v1:${createHash("sha256").update(JSON.stringify([agent, sessionId, eventType, invocationId])).digest("hex")}` : randomUUID();
71
+ const toolOutcome = toolOutcomeFor(parsed, agent);
72
+ const moveId = CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && invocationId ? `posttool:v1:${createHash("sha256").update(JSON.stringify([agent, sessionId, eventType, invocationId])).digest("hex")}` : randomUUID();
27
73
  return {
28
74
  moveId,
29
75
  capturedAt: Date.now(),
@@ -45,7 +91,8 @@ function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, reposito
45
91
  },
46
92
  envelopeVersion: AGENT_ENVELOPE_VERSION,
47
93
  producer: agent,
48
- ...invocationId ? { invocationId } : {}
94
+ ...invocationId ? { invocationId } : {},
95
+ ...toolOutcome ? { toolOutcome } : {}
49
96
  };
50
97
  }
51
98
  var MAX_CLASSIFIER_ID_CHARS = 256;
@@ -63,17 +110,18 @@ function commitAttributionFromEnvironment(env, workspaceId) {
63
110
  return candidates.length === 1 ? candidates[0] : void 0;
64
111
  }
65
112
  function toCommitMove(commit, cliVersion, cwd, context = {}) {
66
- const { repository, repoFullName, repoSyncId, workspaceId, attribution } = context;
113
+ const { repository, repoFullName, repoSyncId, workspaceId, attribution, capturedAt } = context;
67
114
  const validatedRepoSyncId = isValidRepoSyncId(repoSyncId) ? repoSyncId : void 0;
68
115
  const validatedWorkspaceId = workspaceId && isCanonicalWorkspaceId(workspaceId) ? workspaceId : void 0;
69
116
  const validatedAttribution = validatedWorkspaceId ? attribution : void 0;
117
+ const validatedCapturedAt = typeof capturedAt === "number" && Number.isSafeInteger(capturedAt) && capturedAt > 0 ? capturedAt : Date.now();
70
118
  const fullSha = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(commit.sha);
71
119
  const moveId = validatedRepoSyncId && validatedWorkspaceId && fullSha ? `commit:v2:${createHash("sha256").update(
72
120
  JSON.stringify(["commit:v2", validatedRepoSyncId, validatedWorkspaceId, commit.sha])
73
121
  ).digest("hex")}` : `commit:v2:unscoped:${randomUUID()}`;
74
122
  const base = {
75
123
  moveId,
76
- capturedAt: Date.now(),
124
+ capturedAt: validatedCapturedAt,
77
125
  sessionId: validatedAttribution?.sessionId ?? "",
78
126
  eventType: "git.commit",
79
127
  payload: {
@@ -110,6 +158,7 @@ function shouldFlushAfter(eventType) {
110
158
 
111
159
  export {
112
160
  postToolInvocationId,
161
+ toolOutcomeFor,
113
162
  toMove,
114
163
  commitAttributionFromEnvironment,
115
164
  toCommitMove,