@primitive.ai/prim 0.1.0-alpha.62 → 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,
@@ -153,10 +153,10 @@ var CREDENTIAL_FILE_MODE = 384;
153
153
  var REFRESH_THRESHOLD_MS = 6e4;
154
154
  var DEFAULT_API_URL = "https://api.getprimitive.ai";
155
155
  var AUTH_EXPIRED_MESSAGE = "Authentication expired. Run `prim auth login` to re-authenticate.";
156
- function loadEnvFile() {
156
+ function loadEnvFile(cwd = process.cwd()) {
157
157
  const envVars = {};
158
158
  for (const file of [".env.local", ".env"]) {
159
- const filePath = resolve(process.cwd(), file);
159
+ const filePath = resolve(cwd, file);
160
160
  if (!existsSync2(filePath)) continue;
161
161
  const content = readFileSync2(filePath, "utf-8");
162
162
  for (const line of content.split("\n")) {
@@ -194,8 +194,11 @@ function resolveAuthCredential() {
194
194
  return envToken ? { token: envToken, source: "env_file" } : void 0;
195
195
  }
196
196
  function getSiteUrl() {
197
- if (process.env.PRIM_API_URL) return process.env.PRIM_API_URL;
198
- return loadEnvFile().PRIM_API_URL ?? DEFAULT_API_URL;
197
+ return getSiteUrlForCwd(process.cwd(), process.env.PRIM_API_URL);
198
+ }
199
+ function getSiteUrlForCwd(cwd, primApiUrl) {
200
+ if (primApiUrl) return primApiUrl;
201
+ return loadEnvFile(cwd).PRIM_API_URL ?? DEFAULT_API_URL;
199
202
  }
200
203
  function getJwtExpiry(token) {
201
204
  const parts = token.split(".");