@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.
- package/README.md +39 -1
- package/SKILL.md +1 -1
- package/dist/{chunk-XPVNH3J6.js → chunk-2KW7DVFJ.js} +121 -130
- package/dist/chunk-3EEFL6NJ.js +529 -0
- package/dist/chunk-4A2XB2PC.js +310 -0
- package/dist/{chunk-G5D3FF3D.js → chunk-BQUJ4AFJ.js} +1 -83
- package/dist/chunk-CUE2LKLR.js +155 -0
- package/dist/chunk-FI3LIRCB.js +262 -0
- package/dist/{chunk-CPSE3Y3T.js → chunk-H64F6QHV.js} +27 -8
- package/dist/chunk-KKP2NYZ3.js +505 -0
- package/dist/{chunk-JQFN6LBO.js → chunk-LEMX3IOG.js} +6 -37
- package/dist/{chunk-U6L2RU67.js → chunk-LMRYZL3D.js} +52 -45
- package/dist/{chunk-XUAP572Z.js → chunk-RO3F6JYV.js} +8 -7
- package/dist/{chunk-TDRXHBEX.js → chunk-TJTVHDJ5.js} +41 -18
- package/dist/{chunk-TCDUH7AN.js → chunk-WB7AUZ4C.js} +17 -0
- package/dist/chunk-XGADQPCI.js +26 -0
- package/dist/{chunk-UN5O5SYG.js → chunk-YWO7O6QG.js} +14 -53
- package/dist/chunk-Z7YPFWAZ.js +917 -0
- package/dist/{chunk-BDGQEJA5.js → chunk-ZOSNEO5N.js} +2 -2
- package/dist/daemon/server.js +11804 -470
- package/dist/hook-runtime/daemon/server.js +13353 -0
- package/dist/hook-runtime/hooks/post-commit.js +1128 -0
- package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
- package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
- package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
- package/dist/hook-runtime/hooks/pre-tool-use.js +7099 -0
- package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
- package/dist/hook-runtime/hooks/session-end.js +495 -0
- package/dist/hook-runtime/hooks/session-start.js +11284 -0
- package/dist/hook-runtime/index.js +42775 -0
- package/dist/hook-runtime/statusline-main.js +765 -0
- package/dist/hooks/post-commit.js +7 -4
- package/dist/hooks/post-rewrite.js +7 -4
- package/dist/hooks/post-tool-use.js +64 -32
- package/dist/hooks/pre-commit.js +4 -3
- package/dist/hooks/pre-tool-use.js +118 -38
- package/dist/hooks/prim-hook.js +72 -27
- package/dist/hooks/session-end.js +5 -4
- package/dist/hooks/session-start.js +36 -25
- package/dist/index.js +15039 -2555
- package/dist/statusline-main.js +488 -96
- package/package.json +11 -5
- package/dist/chunk-CZ24W5QU.js +0 -123
- package/dist/chunk-RTLFE6EZ.js +0 -518
- package/dist/chunk-UTKQTZHL.js +0 -88
|
@@ -2,21 +2,24 @@
|
|
|
2
2
|
import {
|
|
3
3
|
commitAttributionFromEnvironment,
|
|
4
4
|
toCommitMove
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-H64F6QHV.js";
|
|
6
6
|
import {
|
|
7
7
|
appendMove,
|
|
8
8
|
resolveOrg
|
|
9
|
-
} from "../chunk-
|
|
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-
|
|
19
|
-
import "../chunk-
|
|
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-
|
|
5
|
+
} from "../chunk-H64F6QHV.js";
|
|
6
6
|
import {
|
|
7
7
|
appendMove,
|
|
8
8
|
resolveOrg
|
|
9
|
-
} from "../chunk-
|
|
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-
|
|
19
|
-
import "../chunk-
|
|
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-
|
|
4
|
+
} from "../chunk-XGADQPCI.js";
|
|
7
5
|
import {
|
|
8
6
|
enrichHookPayloadWithFileRefs,
|
|
9
7
|
preserveHookFileMetadata
|
|
10
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-ZOSNEO5N.js";
|
|
11
9
|
import {
|
|
12
10
|
postToolInvocationId,
|
|
13
11
|
toMove,
|
|
14
12
|
toolOutcomeFor
|
|
15
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-H64F6QHV.js";
|
|
16
14
|
import {
|
|
17
15
|
appendMove,
|
|
18
16
|
resolveOrg
|
|
19
|
-
} from "../chunk-
|
|
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-
|
|
29
|
+
} from "../chunk-FI3LIRCB.js";
|
|
26
30
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
bold,
|
|
32
|
+
color
|
|
33
|
+
} from "../chunk-LEMX3IOG.js";
|
|
29
34
|
import {
|
|
30
35
|
isRepoActiveForCapture,
|
|
31
36
|
repoSyncId,
|
|
32
37
|
resolveRepositoryContext
|
|
33
|
-
} from "../chunk-
|
|
38
|
+
} from "../chunk-TJTVHDJ5.js";
|
|
34
39
|
import {
|
|
35
|
-
getClient
|
|
36
|
-
|
|
40
|
+
getClient,
|
|
41
|
+
terminalSafeLine
|
|
42
|
+
} from "../chunk-Z7YPFWAZ.js";
|
|
37
43
|
import {
|
|
38
44
|
normalizeEnvelope,
|
|
39
45
|
parseAgent
|
|
40
|
-
} from "../chunk-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
});
|
package/dist/hooks/pre-commit.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
checkAffectedDecisions,
|
|
4
4
|
formatDecisionsWarning
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-
|
|
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-
|
|
25
|
+
} from "../chunk-FI3LIRCB.js";
|
|
16
26
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
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-
|
|
34
|
+
} from "../chunk-TJTVHDJ5.js";
|
|
26
35
|
import {
|
|
27
36
|
getClient
|
|
28
|
-
} from "../chunk-
|
|
37
|
+
} from "../chunk-Z7YPFWAZ.js";
|
|
29
38
|
import {
|
|
30
39
|
normalizeEnvelope,
|
|
31
40
|
parseAgent
|
|
32
|
-
} from "../chunk-
|
|
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 = [
|
|
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" ?
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
170
|
-
|
|
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(
|
|
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" &&
|
|
259
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
});
|