@primitive.ai/prim 0.1.0-alpha.56 → 0.1.0-alpha.57
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/dist/{chunk-ROL7SDXX.js → chunk-GQURYENF.js} +1 -1
- package/dist/{chunk-6LAQVM26.js → chunk-GUXBUBQ7.js} +2 -0
- package/dist/chunk-LDQZASIF.js +166 -0
- package/dist/chunk-SWWBK4MP.js +424 -0
- package/dist/{chunk-F5QCFBJ6.js → chunk-TNNHR3EZ.js} +8 -18
- package/dist/{chunk-HSZPTVKU.js → chunk-YIMUIPMD.js} +20 -15
- package/dist/daemon/server.js +7 -0
- package/dist/hooks/post-commit.js +2 -2
- package/dist/hooks/post-tool-use.js +26 -9
- package/dist/hooks/pre-tool-use.js +89 -230
- package/dist/hooks/prim-hook.js +10 -7
- package/dist/hooks/session-start.js +3 -3
- package/dist/index.js +99 -63
- package/dist/statusline-main.js +10 -8
- package/package.json +2 -1
- package/dist/chunk-R5ZJRSLV.js +0 -82
|
@@ -10,8 +10,6 @@ import { fileURLToPath } from "url";
|
|
|
10
10
|
var PKG_NAME = "@primitive.ai/prim";
|
|
11
11
|
var ROOT_WALK_LIMIT = 6;
|
|
12
12
|
var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
|
|
13
|
-
var BIN_CACHE_DIR_SH = "${XDG_CACHE_HOME:-$HOME/.cache}/prim/bin";
|
|
14
|
-
var BIN_CACHE_TTL_MIN_DEFAULT = 1440;
|
|
15
13
|
var resolvedRoot;
|
|
16
14
|
function locateRoot() {
|
|
17
15
|
if (resolvedRoot !== void 0) {
|
|
@@ -24,7 +22,7 @@ function locateRoot() {
|
|
|
24
22
|
try {
|
|
25
23
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
26
24
|
if (manifest.name === PKG_NAME && manifest.bin) {
|
|
27
|
-
resolvedRoot = { dir, bin: manifest.bin };
|
|
25
|
+
resolvedRoot = { dir, version: manifest.version, bin: manifest.bin };
|
|
28
26
|
return resolvedRoot;
|
|
29
27
|
}
|
|
30
28
|
} catch {
|
|
@@ -47,18 +45,23 @@ function binFile(bin) {
|
|
|
47
45
|
}
|
|
48
46
|
return isAbsolute(rel) ? rel : join(root.dir, rel);
|
|
49
47
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
48
|
+
function packageVersion() {
|
|
49
|
+
return locateRoot()?.version ?? null;
|
|
50
|
+
}
|
|
51
|
+
function shellQuote(value) {
|
|
52
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
53
|
+
}
|
|
54
|
+
function pinnedHookCommand(bin, args = "") {
|
|
55
|
+
const version = packageVersion();
|
|
56
|
+
if (!version) throw new Error("cannot determine Primitive package version");
|
|
57
|
+
const suffix = args ? ` ${args}` : "";
|
|
58
|
+
const fallback = `npx --yes -p ${PKG_NAME}@${version} ${bin}${suffix}`;
|
|
59
|
+
const file = binFile(bin);
|
|
60
|
+
if (!file) return fallback;
|
|
61
|
+
return `if [ -x ${shellQuote(process.execPath)} ] && [ -f ${shellQuote(file)} ]; then ${shellQuote(process.execPath)} ${shellQuote(file)}${suffix}; else ${fallback}; fi`;
|
|
59
62
|
}
|
|
60
63
|
function detachedHookShimCommand(bin, args = "") {
|
|
61
|
-
return `payload=$(cat); { trap '' HUP; export npm_config_fetch_retries=2 npm_config_fetch_retry_mintimeout=10000 npm_config_fetch_retry_maxtimeout=10000 npm_config_fetch_timeout=60000; printf '%s' "$payload" | { ${
|
|
64
|
+
return `payload=$(cat); { trap '' HUP; export npm_config_fetch_retries=2 npm_config_fetch_retry_mintimeout=10000 npm_config_fetch_retry_maxtimeout=10000 npm_config_fetch_timeout=60000; printf '%s' "$payload" | { ${pinnedHookCommand(bin, args)}; }; } </dev/null >/dev/null 2>&1 &`;
|
|
62
65
|
}
|
|
63
66
|
function commandMatchesBin(command, bin) {
|
|
64
67
|
if (!command) {
|
|
@@ -68,7 +71,8 @@ function commandMatchesBin(command, bin) {
|
|
|
68
71
|
if (c === bin || c.startsWith(`${bin} `)) {
|
|
69
72
|
return true;
|
|
70
73
|
}
|
|
71
|
-
|
|
74
|
+
const exactBin = new RegExp(`(?:^|\\s)${bin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|;|$)`);
|
|
75
|
+
return c.includes(`command -v ${bin} `) || c.includes(`-p ${PKG_NAME}@`) && exactBin.test(c);
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
// src/lib/bin-cache.ts
|
|
@@ -106,7 +110,8 @@ function warmBinCache() {
|
|
|
106
110
|
|
|
107
111
|
export {
|
|
108
112
|
binFile,
|
|
109
|
-
|
|
113
|
+
packageVersion,
|
|
114
|
+
pinnedHookCommand,
|
|
110
115
|
detachedHookShimCommand,
|
|
111
116
|
commandMatchesBin,
|
|
112
117
|
warmBinCache
|
package/dist/daemon/server.js
CHANGED
|
@@ -1488,6 +1488,13 @@ async function runIngestionLoop() {
|
|
|
1488
1488
|
}
|
|
1489
1489
|
async function handleConflictCheck(params) {
|
|
1490
1490
|
assertCallerEnvMatches(params.callerEnv, getSiteUrl());
|
|
1491
|
+
if (params.protocolVersion === 3) {
|
|
1492
|
+
const { callerEnv: _callerEnv, ...body } = params;
|
|
1493
|
+
return await client.post("/api/cli/decisions/conflict-check", body, {
|
|
1494
|
+
signal: AbortSignal.timeout(HTTP_PROXY_TIMEOUT_MS),
|
|
1495
|
+
quietRefresh: true
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1491
1498
|
if (typeof params.file !== "string") {
|
|
1492
1499
|
throw new Error("conflict_check requires `file: string`");
|
|
1493
1500
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
toCommitMove
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-TNNHR3EZ.js";
|
|
5
5
|
import {
|
|
6
6
|
appendMove,
|
|
7
7
|
resolveOrg
|
|
8
8
|
} from "../chunk-BOTKPLTI.js";
|
|
9
9
|
import {
|
|
10
10
|
isRepoActiveForCapture
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-LDQZASIF.js";
|
|
12
12
|
import "../chunk-DWDEQRWN.js";
|
|
13
13
|
|
|
14
14
|
// src/hooks/post-commit.ts
|
|
@@ -5,24 +5,28 @@ import {
|
|
|
5
5
|
requireDurableIngestAcknowledgement
|
|
6
6
|
} from "../chunk-QY75BQMF.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
} from "../chunk-
|
|
8
|
+
resolvePreflightTargets
|
|
9
|
+
} from "../chunk-SWWBK4MP.js";
|
|
10
10
|
import {
|
|
11
11
|
toMove
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-TNNHR3EZ.js";
|
|
13
13
|
import {
|
|
14
14
|
appendMove,
|
|
15
15
|
resolveOrg
|
|
16
16
|
} from "../chunk-BOTKPLTI.js";
|
|
17
|
+
import {
|
|
18
|
+
scrubFromCwd
|
|
19
|
+
} from "../chunk-GUXBUBQ7.js";
|
|
17
20
|
import {
|
|
18
21
|
getOrCreateWorkspaceId
|
|
19
22
|
} from "../chunk-IMAIBPUC.js";
|
|
20
23
|
import {
|
|
21
24
|
warmBinCache
|
|
22
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-YIMUIPMD.js";
|
|
23
26
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
isRepoActive,
|
|
28
|
+
repoSyncId
|
|
29
|
+
} from "../chunk-LDQZASIF.js";
|
|
26
30
|
import {
|
|
27
31
|
getClient
|
|
28
32
|
} from "../chunk-DWDEQRWN.js";
|
|
@@ -66,7 +70,7 @@ function isVerdictFooterContext(value) {
|
|
|
66
70
|
|
|
67
71
|
// src/hooks/post-tool-use.ts
|
|
68
72
|
var STDIN_TIMEOUT_MS = 1e3;
|
|
69
|
-
var EDITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
|
|
73
|
+
var EDITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit", "Bash"]);
|
|
70
74
|
var CODEX_EDITING_TOOLS = /* @__PURE__ */ new Set(["apply_patch"]);
|
|
71
75
|
var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
|
|
72
76
|
function editingToolsFor(agent) {
|
|
@@ -146,13 +150,26 @@ async function main() {
|
|
|
146
150
|
return;
|
|
147
151
|
}
|
|
148
152
|
const cwd = parsed.cwd ?? process.cwd();
|
|
149
|
-
if (!
|
|
153
|
+
if (!isRepoActive(cwd) || !repoSyncId(cwd)) {
|
|
150
154
|
emit();
|
|
151
155
|
return;
|
|
152
156
|
}
|
|
157
|
+
if (agent === "claude_code") {
|
|
158
|
+
const targets = resolvePreflightTargets({
|
|
159
|
+
toolName,
|
|
160
|
+
toolInput: envelope.tool_input,
|
|
161
|
+
agent,
|
|
162
|
+
cwd
|
|
163
|
+
});
|
|
164
|
+
if (targets.mutation === "none") {
|
|
165
|
+
emit();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
153
169
|
const identity = getOrCreateWorkspaceId(cwd);
|
|
154
170
|
const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
|
|
155
|
-
const
|
|
171
|
+
const invocationId = agent === "claude_code" && typeof envelope.tool_use_id === "string" ? envelope.tool_use_id : void 0;
|
|
172
|
+
const base = toMove(parsed, resolveCliVersion(), agent, workspaceId, invocationId);
|
|
156
173
|
const move = { ...base, payload: scrubFromCwd(parsed, cwd) };
|
|
157
174
|
const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
|
|
158
175
|
try {
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
PREFLIGHT_PROTOCOL_VERSION,
|
|
4
|
+
buildHermesOutput,
|
|
5
|
+
buildHookOutput,
|
|
6
|
+
demoteForMode,
|
|
7
|
+
failOpenHermes,
|
|
8
|
+
failOpenOutput,
|
|
9
|
+
parsePreflightResponse,
|
|
10
|
+
proposalFor,
|
|
11
|
+
readHookMode,
|
|
12
|
+
resolvePreflightTargets,
|
|
13
|
+
resultForPreflight,
|
|
14
|
+
unverifiedResult
|
|
15
|
+
} from "../chunk-SWWBK4MP.js";
|
|
16
|
+
import "../chunk-GUXBUBQ7.js";
|
|
2
17
|
import {
|
|
3
18
|
warmBinCache
|
|
4
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-YIMUIPMD.js";
|
|
5
20
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
21
|
+
isRepoActive,
|
|
22
|
+
repoSyncId
|
|
23
|
+
} from "../chunk-LDQZASIF.js";
|
|
8
24
|
import {
|
|
9
25
|
getClient,
|
|
10
26
|
getSiteUrl
|
|
@@ -17,206 +33,6 @@ import {
|
|
|
17
33
|
parseAgent
|
|
18
34
|
} from "../chunk-TCDUH7AN.js";
|
|
19
35
|
|
|
20
|
-
// src/hooks/pre-tool-use-scoring.ts
|
|
21
|
-
import { isAbsolute, relative, sep } from "path";
|
|
22
|
-
var VERDICT_SEVERITY = {
|
|
23
|
-
allow: 0,
|
|
24
|
-
warn: 1,
|
|
25
|
-
ask: 2,
|
|
26
|
-
deny: 3
|
|
27
|
-
};
|
|
28
|
-
function toRepoRelative(filePath, cwd) {
|
|
29
|
-
const rel = isAbsolute(filePath) ? relative(cwd, filePath) : filePath;
|
|
30
|
-
return sep === "/" ? rel : rel.split(sep).join("/");
|
|
31
|
-
}
|
|
32
|
-
function aggregateCheckResults(results) {
|
|
33
|
-
let worst = "allow";
|
|
34
|
-
for (const r of results) {
|
|
35
|
-
if (r.verdict !== "unavailable" && VERDICT_SEVERITY[r.verdict] > VERDICT_SEVERITY[worst]) {
|
|
36
|
-
worst = r.verdict;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return worst;
|
|
40
|
-
}
|
|
41
|
-
function anyUnverified(results) {
|
|
42
|
-
return results.some((r) => r.verdict === "unavailable" || r.truncated);
|
|
43
|
-
}
|
|
44
|
-
function unverifiedNote(results) {
|
|
45
|
-
const causes = [];
|
|
46
|
-
const unavailable = results.find((r) => r.verdict === "unavailable");
|
|
47
|
-
if (unavailable) {
|
|
48
|
-
causes.push(
|
|
49
|
-
unavailable.unavailable ? `decision check skipped \u2014 ${unavailable.unavailable}` : "decision check skipped \u2014 not verified"
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
if (results.some((r) => r.truncated)) {
|
|
53
|
-
causes.push("decision check partial \u2014 conflict set truncated (per-file cap hit)");
|
|
54
|
-
}
|
|
55
|
-
return causes.map((c) => `[primitive] ${c}`).join("\n");
|
|
56
|
-
}
|
|
57
|
-
function buildHookOutput(aggregate, results, agent2 = "claude_code") {
|
|
58
|
-
if (aggregate === "deny") {
|
|
59
|
-
const reason = results.filter((r) => r.verdict === "deny").map((r) => r.reason).filter((s) => s.length > 0).join("\n\n") || "[primitive] conflict detected (no detail available)";
|
|
60
|
-
return {
|
|
61
|
-
hookSpecificOutput: {
|
|
62
|
-
hookEventName: "PreToolUse",
|
|
63
|
-
permissionDecision: "deny",
|
|
64
|
-
permissionDecisionReason: reason
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
if (agent2 === "codex" && aggregate === "ask") {
|
|
69
|
-
const reason = results.filter((r) => r.verdict === "ask" || r.verdict === "deny").map((r) => r.reason).filter((s) => s.length > 0).join("\n\n");
|
|
70
|
-
const context = results.map((r) => r.additionalContext).filter((s) => s.length > 0).join("\n");
|
|
71
|
-
const merged = [reason, context].filter((s) => s.length > 0).join("\n\n");
|
|
72
|
-
const out = {
|
|
73
|
-
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow" }
|
|
74
|
-
};
|
|
75
|
-
if (merged.length > 0) {
|
|
76
|
-
out.hookSpecificOutput.additionalContext = merged;
|
|
77
|
-
}
|
|
78
|
-
return out;
|
|
79
|
-
}
|
|
80
|
-
if (aggregate === "ask") {
|
|
81
|
-
const reason = results.filter((r) => r.verdict === "ask" || r.verdict === "deny").map((r) => r.reason).filter((s) => s.length > 0).join("\n\n") || "[primitive] please confirm this edit";
|
|
82
|
-
const additionalContext = results.map((r) => r.additionalContext).filter((s) => s.length > 0).join("\n");
|
|
83
|
-
const out = {
|
|
84
|
-
hookSpecificOutput: {
|
|
85
|
-
hookEventName: "PreToolUse",
|
|
86
|
-
permissionDecision: "ask",
|
|
87
|
-
permissionDecisionReason: reason
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
if (additionalContext.length > 0) {
|
|
91
|
-
out.hookSpecificOutput.additionalContext = additionalContext;
|
|
92
|
-
}
|
|
93
|
-
return out;
|
|
94
|
-
}
|
|
95
|
-
const notes = [
|
|
96
|
-
...results.map((r) => r.additionalContext).filter((s) => s.length > 0),
|
|
97
|
-
anyUnverified(results) ? unverifiedNote(results) : ""
|
|
98
|
-
].filter((s) => s.length > 0).join("\n");
|
|
99
|
-
if (notes.length > 0) {
|
|
100
|
-
return {
|
|
101
|
-
hookSpecificOutput: {
|
|
102
|
-
hookEventName: "PreToolUse",
|
|
103
|
-
permissionDecision: "allow",
|
|
104
|
-
additionalContext: notes
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
hookSpecificOutput: {
|
|
110
|
-
hookEventName: "PreToolUse",
|
|
111
|
-
permissionDecision: "allow"
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function failOpenOutput() {
|
|
116
|
-
return {
|
|
117
|
-
hookSpecificOutput: {
|
|
118
|
-
hookEventName: "PreToolUse",
|
|
119
|
-
permissionDecision: "allow"
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
function buildHermesOutput(aggregate, results) {
|
|
124
|
-
if (aggregate !== "deny" && aggregate !== "ask") {
|
|
125
|
-
return {};
|
|
126
|
-
}
|
|
127
|
-
const reason = results.filter((r) => r.verdict === "deny" || r.verdict === "ask").map((r) => r.reason).filter((s) => s.length > 0).join("\n\n");
|
|
128
|
-
const directive = results.map((r) => r.additionalContext).filter((s) => s.length > 0).join("\n");
|
|
129
|
-
const message = [reason, directive].filter((s) => s.length > 0).join("\n\n") || "[primitive] conflict detected (no detail available)";
|
|
130
|
-
return { action: "block", message };
|
|
131
|
-
}
|
|
132
|
-
function failOpenHermes() {
|
|
133
|
-
return {};
|
|
134
|
-
}
|
|
135
|
-
var SUPPORTED_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
|
|
136
|
-
var APPLY_PATCH_FILE_RE = /^\*\*\* (?:Update|Add|Delete) File: (?<path>.+)$/;
|
|
137
|
-
var MOVE_FILE_RE = /^\*\*\* Move File:\s*(?<src>.+?)\s*->\s*(?<dst>.+?)\s*$/;
|
|
138
|
-
var LINE_SPLIT_RE = /\r?\n/;
|
|
139
|
-
function parseApplyPatchPaths(command) {
|
|
140
|
-
const paths = /* @__PURE__ */ new Set();
|
|
141
|
-
for (const line of command.split(LINE_SPLIT_RE)) {
|
|
142
|
-
const path = APPLY_PATCH_FILE_RE.exec(line)?.groups?.path?.trim();
|
|
143
|
-
if (path) {
|
|
144
|
-
paths.add(path);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const move = MOVE_FILE_RE.exec(line)?.groups;
|
|
148
|
-
if (move?.src) {
|
|
149
|
-
paths.add(move.src.trim());
|
|
150
|
-
if (move.dst) {
|
|
151
|
-
paths.add(move.dst.trim());
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return Array.from(paths);
|
|
156
|
-
}
|
|
157
|
-
function extractCodexFilePaths(toolName, toolInput) {
|
|
158
|
-
if (toolName !== "apply_patch") {
|
|
159
|
-
return [];
|
|
160
|
-
}
|
|
161
|
-
if (!toolInput || typeof toolInput !== "object") {
|
|
162
|
-
return [];
|
|
163
|
-
}
|
|
164
|
-
const command = toolInput.command;
|
|
165
|
-
return typeof command === "string" ? parseApplyPatchPaths(command) : [];
|
|
166
|
-
}
|
|
167
|
-
var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
|
|
168
|
-
function extractHermesFilePaths(toolName, toolInput) {
|
|
169
|
-
if (!HERMES_EDITING_TOOLS.has(toolName)) {
|
|
170
|
-
return [];
|
|
171
|
-
}
|
|
172
|
-
if (!toolInput || typeof toolInput !== "object") {
|
|
173
|
-
return [];
|
|
174
|
-
}
|
|
175
|
-
const input = toolInput;
|
|
176
|
-
if (toolName === "patch" && input.mode === "patch") {
|
|
177
|
-
return typeof input.patch === "string" ? parseApplyPatchPaths(input.patch) : [];
|
|
178
|
-
}
|
|
179
|
-
return typeof input.path === "string" && input.path.length > 0 ? [input.path] : [];
|
|
180
|
-
}
|
|
181
|
-
function extractFilePaths(toolName, toolInput, agent2 = "claude_code") {
|
|
182
|
-
if (agent2 === "codex") {
|
|
183
|
-
return extractCodexFilePaths(toolName, toolInput);
|
|
184
|
-
}
|
|
185
|
-
if (agent2 === "hermes") {
|
|
186
|
-
return extractHermesFilePaths(toolName, toolInput);
|
|
187
|
-
}
|
|
188
|
-
if (!SUPPORTED_TOOLS.has(toolName)) {
|
|
189
|
-
return [];
|
|
190
|
-
}
|
|
191
|
-
if (!toolInput || typeof toolInput !== "object") {
|
|
192
|
-
return [];
|
|
193
|
-
}
|
|
194
|
-
const input = toolInput;
|
|
195
|
-
if (typeof input.file_path === "string" && input.file_path.length > 0) {
|
|
196
|
-
return [input.file_path];
|
|
197
|
-
}
|
|
198
|
-
return [];
|
|
199
|
-
}
|
|
200
|
-
function readHookMode(env) {
|
|
201
|
-
if (env.PRIM_BYPASS === "1" || env.PRIM_BYPASS === "true") {
|
|
202
|
-
return "off";
|
|
203
|
-
}
|
|
204
|
-
const mode = env.PRIM_HOOK_MODE;
|
|
205
|
-
if (mode === "off" || mode === "warn") {
|
|
206
|
-
return mode;
|
|
207
|
-
}
|
|
208
|
-
return "block";
|
|
209
|
-
}
|
|
210
|
-
function demoteForMode(verdict, mode) {
|
|
211
|
-
if (mode === "off") {
|
|
212
|
-
return "allow";
|
|
213
|
-
}
|
|
214
|
-
if (mode === "warn" && (verdict === "ask" || verdict === "deny")) {
|
|
215
|
-
return "warn";
|
|
216
|
-
}
|
|
217
|
-
return verdict;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
36
|
// src/hooks/pre-tool-use.ts
|
|
221
37
|
var HOOK_TIMEOUT_MS = 4500;
|
|
222
38
|
var STDIN_TIMEOUT_MS = 1e3;
|
|
@@ -246,23 +62,19 @@ function emit(output) {
|
|
|
246
62
|
function failOpen() {
|
|
247
63
|
return agent === "hermes" ? failOpenHermes() : failOpenOutput();
|
|
248
64
|
}
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
65
|
+
function invocationId(envelope) {
|
|
66
|
+
const value = agent === "hermes" ? envelope.extra?.tool_call_id : envelope.tool_use_id;
|
|
67
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
68
|
+
}
|
|
69
|
+
function emitUnverified(message) {
|
|
70
|
+
const result = unverifiedResult(message);
|
|
71
|
+
if (agent === "hermes") {
|
|
72
|
+
process.stderr.write(`[primitive] ${message}
|
|
73
|
+
`);
|
|
74
|
+
emit(failOpenHermes());
|
|
75
|
+
} else {
|
|
76
|
+
emit(buildHookOutput("allow", [result], agent));
|
|
259
77
|
}
|
|
260
|
-
const client = getClient();
|
|
261
|
-
return await client.post(
|
|
262
|
-
"/api/cli/decisions/conflict-check",
|
|
263
|
-
{ file },
|
|
264
|
-
{ signal: AbortSignal.timeout(HOOK_TIMEOUT_MS) }
|
|
265
|
-
);
|
|
266
78
|
}
|
|
267
79
|
async function main() {
|
|
268
80
|
warmBinCache();
|
|
@@ -295,28 +107,75 @@ async function main() {
|
|
|
295
107
|
}
|
|
296
108
|
const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
|
|
297
109
|
const cwd = typeof envelope.cwd === "string" && envelope.cwd.length > 0 ? envelope.cwd : process.cwd();
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
110
|
+
const targets = resolvePreflightTargets({
|
|
111
|
+
toolName,
|
|
112
|
+
toolInput: envelope.tool_input,
|
|
113
|
+
agent,
|
|
114
|
+
cwd
|
|
115
|
+
});
|
|
116
|
+
if (targets.mutation === "none") {
|
|
302
117
|
emit(failOpen());
|
|
303
118
|
return;
|
|
304
119
|
}
|
|
305
|
-
if (!
|
|
120
|
+
if (!isRepoActive(cwd)) {
|
|
306
121
|
emit(failOpen());
|
|
307
122
|
return;
|
|
308
123
|
}
|
|
309
|
-
|
|
124
|
+
if (targets.paths.length === 0) {
|
|
125
|
+
emitUnverified("mutation targets could not be determined; enforcement not verified");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const binding = repoSyncId(cwd);
|
|
129
|
+
const sessionId = envelope.session_id;
|
|
130
|
+
const callId = invocationId(envelope);
|
|
131
|
+
if (!binding || typeof sessionId !== "string" || !sessionId || !callId) {
|
|
132
|
+
emitUnverified("repository binding or tool invocation identity is unavailable");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const request = {
|
|
136
|
+
protocolVersion: PREFLIGHT_PROTOCOL_VERSION,
|
|
137
|
+
agent,
|
|
138
|
+
sessionId,
|
|
139
|
+
invocationId: callId,
|
|
140
|
+
repoSyncId: binding,
|
|
141
|
+
paths: targets.paths,
|
|
142
|
+
coverage: targets.coverage,
|
|
143
|
+
proposal: proposalFor(envelope.tool_input)
|
|
144
|
+
};
|
|
145
|
+
let result;
|
|
310
146
|
try {
|
|
311
|
-
|
|
147
|
+
const fromDaemon = await daemonRequest(
|
|
148
|
+
"conflict_check",
|
|
149
|
+
{ ...request, callerEnv: getSiteUrl() },
|
|
150
|
+
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
151
|
+
);
|
|
152
|
+
let response = parsePreflightResponse(fromDaemon);
|
|
153
|
+
if (!response) {
|
|
154
|
+
const direct = await getClient().post("/api/cli/decisions/conflict-check", request, {
|
|
155
|
+
signal: AbortSignal.timeout(HOOK_TIMEOUT_MS),
|
|
156
|
+
quietRefresh: true
|
|
157
|
+
});
|
|
158
|
+
response = parsePreflightResponse(direct);
|
|
159
|
+
}
|
|
160
|
+
if (!response) {
|
|
161
|
+
emitUnverified("enforcement service returned an incompatible response");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
result = resultForPreflight(response);
|
|
312
165
|
} catch {
|
|
313
|
-
|
|
166
|
+
emitUnverified("enforcement service unavailable; change was not verified");
|
|
314
167
|
return;
|
|
315
168
|
}
|
|
316
|
-
const
|
|
317
|
-
|
|
169
|
+
const aggregate = demoteForMode(
|
|
170
|
+
result.verdict === "unavailable" ? "allow" : result.verdict,
|
|
171
|
+
mode
|
|
172
|
+
);
|
|
173
|
+
if (agent === "hermes" && (aggregate === "warn" || result.verdict === "unavailable")) {
|
|
174
|
+
process.stderr.write(`[primitive] ${result.reason || result.unavailable}
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
318
177
|
emit(
|
|
319
|
-
agent === "hermes" ? buildHermesOutput(aggregate,
|
|
178
|
+
agent === "hermes" ? buildHermesOutput(aggregate, [result]) : buildHookOutput(aggregate, [result], agent)
|
|
320
179
|
);
|
|
321
180
|
}
|
|
322
181
|
main().catch(() => {
|
package/dist/hooks/prim-hook.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
scrubFromCwd
|
|
4
|
-
} from "../chunk-6LAQVM26.js";
|
|
5
2
|
import {
|
|
6
3
|
shouldFlushAfter,
|
|
7
4
|
toMove
|
|
8
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-TNNHR3EZ.js";
|
|
9
6
|
import {
|
|
10
7
|
appendMove,
|
|
11
8
|
resolveOrg
|
|
12
9
|
} from "../chunk-BOTKPLTI.js";
|
|
10
|
+
import {
|
|
11
|
+
scrubFromCwd
|
|
12
|
+
} from "../chunk-GUXBUBQ7.js";
|
|
13
13
|
import {
|
|
14
14
|
buildHookOutput,
|
|
15
15
|
handoffHookOutput
|
|
@@ -25,10 +25,10 @@ import {
|
|
|
25
25
|
} from "../chunk-IMAIBPUC.js";
|
|
26
26
|
import {
|
|
27
27
|
warmBinCache
|
|
28
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-YIMUIPMD.js";
|
|
29
29
|
import {
|
|
30
30
|
isRepoActiveForCapture
|
|
31
|
-
} from "../chunk-
|
|
31
|
+
} from "../chunk-LDQZASIF.js";
|
|
32
32
|
import "../chunk-DWDEQRWN.js";
|
|
33
33
|
import {
|
|
34
34
|
normalizeEnvelope,
|
|
@@ -90,7 +90,10 @@ async function main() {
|
|
|
90
90
|
const identity = getOrCreateWorkspaceId(cwd);
|
|
91
91
|
const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
|
|
92
92
|
try {
|
|
93
|
-
const
|
|
93
|
+
const successfulPost = parsed.hook_event_name === "PostToolUse";
|
|
94
|
+
const rawInvocationId = agent === "hermes" ? parsed.extra?.tool_call_id : parsed.tool_use_id;
|
|
95
|
+
const invocationId = successfulPost && typeof rawInvocationId === "string" ? rawInvocationId : void 0;
|
|
96
|
+
const base = toMove(parsed, resolveCliVersion(), agent, workspaceId, invocationId);
|
|
94
97
|
const move = { ...base, payload: scrubFromCwd(parsed, cwd) };
|
|
95
98
|
const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
|
|
96
99
|
appendMove(move, orgId);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
hasUsableCodexGuidance,
|
|
4
4
|
refreshClaudePlugins
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-GQURYENF.js";
|
|
6
6
|
import {
|
|
7
7
|
buildHookOutput,
|
|
8
8
|
handoffHookOutput
|
|
@@ -19,11 +19,11 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
binFile,
|
|
21
21
|
warmBinCache
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-YIMUIPMD.js";
|
|
23
23
|
import {
|
|
24
24
|
gitToplevel,
|
|
25
25
|
isRepoActiveForCapture
|
|
26
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-LDQZASIF.js";
|
|
27
27
|
import {
|
|
28
28
|
getSiteUrl,
|
|
29
29
|
isSessionEnded
|