eminiar 0.13.0 → 0.13.2
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/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +43 -15
- package/bin/_resolve.sh +7 -4
- package/bin/em-hook +2 -1
- package/bin/em-server +2 -1
- package/bin/eminiar +2 -1
- package/dist/cli.js +10113 -8385
- package/dist/{hook-async-4MIP3VHX.js → hook-async-3TLK23ZY.js} +3 -3
- package/dist/{hook-chunk-IYHVLMJ4.js → hook-chunk-ETYHCPPH.js} +52 -25
- package/dist/{hook-chunk-77I6QQ43.js → hook-chunk-MVSMFCKL.js} +2 -2
- package/dist/{hook-chunk-MVOBVFQL.js → hook-chunk-ZLRLOPLR.js} +98 -33
- package/dist/{hook-hooks-U6GDOQSY.js → hook-hooks-5U2AI6W7.js} +8 -6
- package/dist/hook.js +318 -74
- package/dist/server.js +4417 -4078
- package/dist/ui/app.global.js +78 -72
- package/dist/ui/styles.css +4724 -1508
- package/eminiar.config.schema.json +2 -1
- package/package.json +1 -1
package/dist/hook.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
recordShown,
|
|
24
24
|
renderProjectBrief,
|
|
25
25
|
resolvePrice,
|
|
26
|
+
spawnType,
|
|
26
27
|
subagentStart,
|
|
27
28
|
swarmActivity,
|
|
28
29
|
swarmAgentReturned,
|
|
@@ -32,13 +33,16 @@ import {
|
|
|
32
33
|
swarmSessionStart,
|
|
33
34
|
swarmStampCaller,
|
|
34
35
|
swarmSubagentStop,
|
|
35
|
-
swarmTouch
|
|
36
|
-
|
|
36
|
+
swarmTouch,
|
|
37
|
+
workerLacksTool
|
|
38
|
+
} from "./hook-chunk-ZLRLOPLR.js";
|
|
37
39
|
import {
|
|
38
40
|
GUIDE_ORDER,
|
|
39
41
|
classifyAssistant,
|
|
40
42
|
classifyPrompt,
|
|
41
43
|
classifyToolCall,
|
|
44
|
+
claudeConfigDir,
|
|
45
|
+
claudePluginsDir,
|
|
42
46
|
clip,
|
|
43
47
|
ensureDirs,
|
|
44
48
|
ev,
|
|
@@ -46,13 +50,14 @@ import {
|
|
|
46
50
|
getStorage,
|
|
47
51
|
init_esm_shims,
|
|
48
52
|
projectDir,
|
|
53
|
+
setStorageBusyTimeout,
|
|
49
54
|
withPriority
|
|
50
|
-
} from "./hook-chunk-
|
|
55
|
+
} from "./hook-chunk-ETYHCPPH.js";
|
|
51
56
|
|
|
52
57
|
// src/hook.ts
|
|
53
58
|
init_esm_shims();
|
|
54
59
|
|
|
55
|
-
// src/hooks/
|
|
60
|
+
// src/hooks/budget.ts
|
|
56
61
|
init_esm_shims();
|
|
57
62
|
|
|
58
63
|
// src/hooks/cursor.ts
|
|
@@ -117,6 +122,18 @@ function toolResponseOf(v) {
|
|
|
117
122
|
return v;
|
|
118
123
|
}
|
|
119
124
|
}
|
|
125
|
+
function handlerEvent(parsed, argv) {
|
|
126
|
+
const forced = argv.indexOf("--event");
|
|
127
|
+
const p = forced !== -1 && argv[forced + 1] ? { ...parsed, hook_event_name: argv[forced + 1] } : parsed;
|
|
128
|
+
const event = str(p.hook_event_name);
|
|
129
|
+
const tool = str(p.tool_name);
|
|
130
|
+
if (detectClient(p, argv) !== "cursor") return { event, tool };
|
|
131
|
+
if (event === "afterFileEdit") return { event: CURSOR_EVENTS[event], tool: "Edit" };
|
|
132
|
+
return {
|
|
133
|
+
event: event === void 0 ? void 0 : CURSOR_EVENTS[event] ?? event,
|
|
134
|
+
tool: tool === void 0 ? void 0 : mapToolName(tool)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
120
137
|
function fromCursor(raw) {
|
|
121
138
|
const event = str(raw.hook_event_name) ?? "";
|
|
122
139
|
const roots = Array.isArray(raw.workspace_roots) ? raw.workspace_roots.filter((r) => typeof r === "string") : [];
|
|
@@ -249,6 +266,63 @@ function toCursor(event, out, scope) {
|
|
|
249
266
|
}
|
|
250
267
|
}
|
|
251
268
|
|
|
269
|
+
// src/hooks/budget.ts
|
|
270
|
+
var HOOK_BUSY_TIMEOUT_MS = 250;
|
|
271
|
+
var WRITE_WAIT = {
|
|
272
|
+
SessionStart: 2e3,
|
|
273
|
+
PostToolUse: 2e3,
|
|
274
|
+
SubagentStart: 2e3,
|
|
275
|
+
PreCompact: 2e3,
|
|
276
|
+
SubagentStop: 1500,
|
|
277
|
+
SessionEnd: 1500,
|
|
278
|
+
Stop: 1500
|
|
279
|
+
};
|
|
280
|
+
function busyTimeoutFor(event, tool) {
|
|
281
|
+
if (event && WRITE_WAIT[event]) return WRITE_WAIT[event];
|
|
282
|
+
if ((event === "PreToolUse" || event === "PostToolUseFailure") && (tool === "Agent" || tool === "Task"))
|
|
283
|
+
return 1500;
|
|
284
|
+
return HOOK_BUSY_TIMEOUT_MS;
|
|
285
|
+
}
|
|
286
|
+
function lockWaitFor(raw, argv) {
|
|
287
|
+
let parsed = {};
|
|
288
|
+
try {
|
|
289
|
+
const p = raw.trim() ? JSON.parse(raw) : {};
|
|
290
|
+
if (p && typeof p === "object" && !Array.isArray(p)) parsed = p;
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
const { event, tool } = handlerEvent(parsed, argv);
|
|
294
|
+
return busyTimeoutFor(event, tool);
|
|
295
|
+
}
|
|
296
|
+
var HOOK_BUDGET_MS = 3500;
|
|
297
|
+
var LONGER = {
|
|
298
|
+
SessionStart: 8e3,
|
|
299
|
+
PostToolUse: 8e3,
|
|
300
|
+
SubagentStart: 8e3,
|
|
301
|
+
PreCompact: 12e3
|
|
302
|
+
};
|
|
303
|
+
function budgetFor(event) {
|
|
304
|
+
return event && LONGER[event] || HOOK_BUDGET_MS;
|
|
305
|
+
}
|
|
306
|
+
var startedAt;
|
|
307
|
+
var budgetMs = HOOK_BUDGET_MS;
|
|
308
|
+
function startBudget(ms = HOOK_BUDGET_MS, now = Date.now()) {
|
|
309
|
+
startedAt = now;
|
|
310
|
+
budgetMs = ms;
|
|
311
|
+
}
|
|
312
|
+
function clearBudget() {
|
|
313
|
+
startedAt = void 0;
|
|
314
|
+
}
|
|
315
|
+
function budgetLeft(now = Date.now()) {
|
|
316
|
+
if (startedAt === void 0) return Number.POSITIVE_INFINITY;
|
|
317
|
+
return Math.max(0, budgetMs - (now - startedAt));
|
|
318
|
+
}
|
|
319
|
+
function hasBudget(needMs, now = Date.now()) {
|
|
320
|
+
return budgetLeft(now) >= needMs;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/hooks/dispatch.ts
|
|
324
|
+
init_esm_shims();
|
|
325
|
+
|
|
252
326
|
// src/hooks/index.ts
|
|
253
327
|
init_esm_shims();
|
|
254
328
|
|
|
@@ -1246,7 +1320,6 @@ function budgetWarnings(db, ctx, sessionId, now = Date.now()) {
|
|
|
1246
1320
|
init_esm_shims();
|
|
1247
1321
|
import { createHash } from "crypto";
|
|
1248
1322
|
import { closeSync, existsSync as existsSync2, openSync, readdirSync, readSync, statSync } from "fs";
|
|
1249
|
-
import { homedir as homedir2 } from "os";
|
|
1250
1323
|
import { basename, dirname, join as join2, resolve, sep } from "path";
|
|
1251
1324
|
|
|
1252
1325
|
// src/usage/transcript.ts
|
|
@@ -1475,7 +1548,7 @@ function isValidSessionId(id) {
|
|
|
1475
1548
|
return SESSION_ID_RE.test(id);
|
|
1476
1549
|
}
|
|
1477
1550
|
function claudeDir() {
|
|
1478
|
-
return process.env.EMINIAR_CLAUDE_DIR ||
|
|
1551
|
+
return process.env.EMINIAR_CLAUDE_DIR || claudeConfigDir();
|
|
1479
1552
|
}
|
|
1480
1553
|
function insideDir(path, dir) {
|
|
1481
1554
|
const d = resolve(dir);
|
|
@@ -1727,6 +1800,9 @@ function ingestSession(db, root, sessionId, transcriptPath, opts = {}) {
|
|
|
1727
1800
|
}
|
|
1728
1801
|
|
|
1729
1802
|
// src/hooks/capture.ts
|
|
1803
|
+
var RECALL_NEEDS_MS = 600;
|
|
1804
|
+
var ADJUDICATOR_NEEDS_MS = 1500;
|
|
1805
|
+
var BEST_EFFORT_NEEDS_MS = 1e3;
|
|
1730
1806
|
var INDEXED = /* @__PURE__ */ new Set([
|
|
1731
1807
|
"decision",
|
|
1732
1808
|
"constraint",
|
|
@@ -1788,13 +1864,23 @@ function ingestUsage(db, input, sessionId) {
|
|
|
1788
1864
|
}
|
|
1789
1865
|
var userPromptSubmit = async (input) => {
|
|
1790
1866
|
if (!input.prompt?.trim()) return void 0;
|
|
1791
|
-
const { db, sessionId } = open(input);
|
|
1792
|
-
db.sessionUpsert({ id: sessionId, last_prompt: clip(input.prompt, 600) });
|
|
1793
|
-
persist(db, sessionId, withPriority(sessionId, classifyPrompt(input.prompt)));
|
|
1794
1867
|
const ctx = getContext(input.cwd);
|
|
1868
|
+
const db = getStorage(ctx, { vectors: false });
|
|
1869
|
+
const sessionId = input.session_id || "unknown";
|
|
1870
|
+
try {
|
|
1871
|
+
db.sessionUpsert({
|
|
1872
|
+
id: sessionId,
|
|
1873
|
+
project_root: ctx.root,
|
|
1874
|
+
transcript_path: input.transcript_path,
|
|
1875
|
+
last_prompt: clip(input.prompt, 600)
|
|
1876
|
+
});
|
|
1877
|
+
persist(db, sessionId, withPriority(sessionId, classifyPrompt(input.prompt)));
|
|
1878
|
+
} catch (err) {
|
|
1879
|
+
hookLog("info", `UserPromptSubmit: prompt not recorded: ${err.message}`);
|
|
1880
|
+
}
|
|
1795
1881
|
const { recall } = ctx.config;
|
|
1796
1882
|
const parts = [];
|
|
1797
|
-
if (recall.onPrompt) {
|
|
1883
|
+
if (recall.onPrompt && hasBudget(RECALL_NEEDS_MS)) {
|
|
1798
1884
|
try {
|
|
1799
1885
|
const r = await recallForText(db, input.prompt, {
|
|
1800
1886
|
maxMemories: recall.maxMemories,
|
|
@@ -1804,15 +1890,18 @@ var userPromptSubmit = async (input) => {
|
|
|
1804
1890
|
trace: true,
|
|
1805
1891
|
path: "prompt"
|
|
1806
1892
|
});
|
|
1807
|
-
recordShown(db, sessionId, r.keys, { path: "prompt", trace: r.trace });
|
|
1808
1893
|
if (r.text) parts.push(r.text);
|
|
1894
|
+
try {
|
|
1895
|
+
recordShown(db, sessionId, r.keys, { path: "prompt", trace: r.trace });
|
|
1896
|
+
} catch {
|
|
1897
|
+
}
|
|
1809
1898
|
} catch {
|
|
1810
1899
|
}
|
|
1811
1900
|
}
|
|
1812
1901
|
let systemMessage;
|
|
1813
|
-
if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
|
|
1902
|
+
if (ctx.config.adjudicator.enabled && input.client !== "cursor" && hasBudget(ADJUDICATOR_NEEDS_MS)) {
|
|
1814
1903
|
try {
|
|
1815
|
-
const { onPrompt } = await import("./hook-hooks-
|
|
1904
|
+
const { onPrompt } = await import("./hook-hooks-5U2AI6W7.js");
|
|
1816
1905
|
const a = await onPrompt(ctx, db, input, sessionId);
|
|
1817
1906
|
if (a?.context) parts.push(a.context);
|
|
1818
1907
|
systemMessage = a?.systemMessage;
|
|
@@ -1870,7 +1959,7 @@ var postToolUse = async (input) => {
|
|
|
1870
1959
|
const ctx = getContext(input.cwd);
|
|
1871
1960
|
if (ctx.config.adjudicator.enabled) {
|
|
1872
1961
|
try {
|
|
1873
|
-
const { onPostAgent } = await import("./hook-hooks-
|
|
1962
|
+
const { onPostAgent } = await import("./hook-hooks-5U2AI6W7.js");
|
|
1874
1963
|
onPostAgent(ctx, db, input);
|
|
1875
1964
|
} catch {
|
|
1876
1965
|
}
|
|
@@ -1878,14 +1967,43 @@ var postToolUse = async (input) => {
|
|
|
1878
1967
|
}
|
|
1879
1968
|
return void 0;
|
|
1880
1969
|
};
|
|
1970
|
+
function bestEffortWrites(db) {
|
|
1971
|
+
let done = false;
|
|
1972
|
+
return (what, fn) => {
|
|
1973
|
+
if (done) return;
|
|
1974
|
+
if (!hasBudget(BEST_EFFORT_NEEDS_MS)) {
|
|
1975
|
+
done = true;
|
|
1976
|
+
hookLog("info", `PostToolUseFailure: ${what} skipped: out of time`);
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
try {
|
|
1980
|
+
const short = db.adjudicator.withLockWait?.bind(db.adjudicator);
|
|
1981
|
+
if (short) short(HOOK_BUSY_TIMEOUT_MS, fn);
|
|
1982
|
+
else fn();
|
|
1983
|
+
} catch (err) {
|
|
1984
|
+
done = true;
|
|
1985
|
+
hookLog("info", `PostToolUseFailure: ${what} not recorded: ${err.message}`);
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1881
1989
|
var postToolUseFailure = async (input) => {
|
|
1882
1990
|
if (!input.tool_name) return void 0;
|
|
1883
|
-
|
|
1884
|
-
if (input.tool_name === "Agent" || input.tool_name === "Task")
|
|
1885
|
-
|
|
1886
|
-
const
|
|
1991
|
+
const ctx = getContext(input.cwd);
|
|
1992
|
+
if (input.tool_name === "Agent" || input.tool_name === "Task") swarmAgentReturned(ctx, input);
|
|
1993
|
+
const db = getStorage(ctx, { vectors: false });
|
|
1994
|
+
const sessionId = input.session_id || "unknown";
|
|
1995
|
+
const write = bestEffortWrites(db);
|
|
1996
|
+
if (input.agent_id) write("swarm activity", () => swarmActivity(ctx, input, true));
|
|
1997
|
+
write(
|
|
1998
|
+
"session",
|
|
1999
|
+
() => db.sessionUpsert({
|
|
2000
|
+
id: sessionId,
|
|
2001
|
+
project_root: ctx.root,
|
|
2002
|
+
transcript_path: input.transcript_path
|
|
2003
|
+
})
|
|
2004
|
+
);
|
|
1887
2005
|
let recalled;
|
|
1888
|
-
const { recall } =
|
|
2006
|
+
const { recall } = ctx.config;
|
|
1889
2007
|
if (recall.onError && input.error) {
|
|
1890
2008
|
try {
|
|
1891
2009
|
const r = await recallForError(db, input.error, {
|
|
@@ -1895,7 +2013,7 @@ var postToolUseFailure = async (input) => {
|
|
|
1895
2013
|
});
|
|
1896
2014
|
if (r.text) {
|
|
1897
2015
|
recalled = r.text;
|
|
1898
|
-
recordShown(db, sessionId, r.keys, { path: "error" });
|
|
2016
|
+
write("recall", () => recordShown(db, sessionId, r.keys, { path: "error" }));
|
|
1899
2017
|
}
|
|
1900
2018
|
} catch {
|
|
1901
2019
|
}
|
|
@@ -1904,9 +2022,9 @@ var postToolUseFailure = async (input) => {
|
|
|
1904
2022
|
toolName: input.tool_name,
|
|
1905
2023
|
toolInput: input.tool_input ?? {},
|
|
1906
2024
|
error: input.error ?? "tool failed",
|
|
1907
|
-
root
|
|
2025
|
+
root: ctx.root
|
|
1908
2026
|
}).filter((d) => d.category !== "tool_call");
|
|
1909
|
-
persist(db, sessionId, withPriority(sessionId, drafts));
|
|
2027
|
+
write("failure", () => persist(db, sessionId, withPriority(sessionId, drafts)));
|
|
1910
2028
|
return recalled ? { hookSpecificOutput: { hookEventName: "PostToolUseFailure", additionalContext: recalled } } : void 0;
|
|
1911
2029
|
};
|
|
1912
2030
|
var subagentStop = (input) => {
|
|
@@ -1946,7 +2064,7 @@ var sessionEnd = async (input) => {
|
|
|
1946
2064
|
ingestUsage(db, input, sessionId);
|
|
1947
2065
|
if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
|
|
1948
2066
|
try {
|
|
1949
|
-
const { onSessionEnd } = await import("./hook-hooks-
|
|
2067
|
+
const { onSessionEnd } = await import("./hook-hooks-5U2AI6W7.js");
|
|
1950
2068
|
onSessionEnd(ctx, db, input);
|
|
1951
2069
|
} catch {
|
|
1952
2070
|
}
|
|
@@ -2080,17 +2198,17 @@ var preCompact = (input) => {
|
|
|
2080
2198
|
// src/hooks/routing.ts
|
|
2081
2199
|
init_esm_shims();
|
|
2082
2200
|
import { realpathSync as realpathSync2, statSync as statSync3 } from "fs";
|
|
2083
|
-
import { homedir as
|
|
2201
|
+
import { homedir as homedir2 } from "os";
|
|
2084
2202
|
import { extname, join as join7, relative, resolve as resolve2 } from "path";
|
|
2085
2203
|
|
|
2086
2204
|
// src/permissions/settings.ts
|
|
2087
2205
|
init_esm_shims();
|
|
2088
2206
|
import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync as statSync2 } from "fs";
|
|
2089
|
-
import { homedir as homedir3 } from "os";
|
|
2090
2207
|
import { join as join3 } from "path";
|
|
2091
2208
|
|
|
2092
2209
|
// src/permissions/rules.ts
|
|
2093
2210
|
init_esm_shims();
|
|
2211
|
+
import { posix } from "path";
|
|
2094
2212
|
function parseRule(raw) {
|
|
2095
2213
|
const m = /^([A-Za-z][\w-]*)(?:\((.*)\))?$/s.exec(raw.trim());
|
|
2096
2214
|
if (!m) return void 0;
|
|
@@ -2099,24 +2217,83 @@ function parseRule(raw) {
|
|
|
2099
2217
|
function escapeRe(s) {
|
|
2100
2218
|
return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
2101
2219
|
}
|
|
2220
|
+
function globSource(glob) {
|
|
2221
|
+
return glob.split("/**/").map(
|
|
2222
|
+
(part) => part.split("**").map(
|
|
2223
|
+
(seg) => seg.split("*").map((s) => s.split("?").map(escapeRe).join("[^/]")).join("[^/]*")
|
|
2224
|
+
).join(".*")
|
|
2225
|
+
).join("/(?:.*/)?");
|
|
2226
|
+
}
|
|
2227
|
+
function anchored(prefix, rest) {
|
|
2228
|
+
const full = posix.normalize(`${prefix}${rest}`);
|
|
2229
|
+
if (prefix && (full === prefix || full.startsWith(`${prefix}/`))) {
|
|
2230
|
+
return literal(prefix) + globSource(full.slice(prefix.length));
|
|
2231
|
+
}
|
|
2232
|
+
return globSource(full);
|
|
2233
|
+
}
|
|
2234
|
+
function literal(s) {
|
|
2235
|
+
return escapeRe(s).replace(/\*/g, "\\*");
|
|
2236
|
+
}
|
|
2237
|
+
function readSource(pattern, root, home) {
|
|
2238
|
+
const dir = pattern.endsWith("/") && pattern.length > 1;
|
|
2239
|
+
const p = dir ? pattern.slice(0, -1) : pattern;
|
|
2240
|
+
let body;
|
|
2241
|
+
if (p.startsWith("**")) body = globSource(p);
|
|
2242
|
+
else if (p.startsWith("//")) body = anchored("", p.slice(1));
|
|
2243
|
+
else if (p === "~" || p.startsWith("~/")) body = anchored(home, p.slice(1));
|
|
2244
|
+
else if (p.startsWith("/")) body = `(?:${anchored(root, p)}|${globSource(posix.normalize(p))})`;
|
|
2245
|
+
else if (p.startsWith("./")) body = anchored(root, p.slice(1));
|
|
2246
|
+
else if (p.includes("/")) body = anchored(root, `/${p}`);
|
|
2247
|
+
else body = `${literal(root)}/(?:.*/)?${globSource(p)}`;
|
|
2248
|
+
return `^${body}(?:/.*)?$`;
|
|
2249
|
+
}
|
|
2102
2250
|
var readRegexCache = /* @__PURE__ */ new Map();
|
|
2103
|
-
function readRegex(pattern) {
|
|
2104
|
-
|
|
2251
|
+
function readRegex(pattern, root, home) {
|
|
2252
|
+
const key = `${root}\0${home}\0${pattern}`;
|
|
2253
|
+
let re = readRegexCache.get(key);
|
|
2105
2254
|
if (!re) {
|
|
2106
|
-
if (readRegexCache.size >=
|
|
2107
|
-
re = new RegExp(
|
|
2108
|
-
|
|
2109
|
-
);
|
|
2110
|
-
readRegexCache.set(pattern, re);
|
|
2255
|
+
if (readRegexCache.size >= 4096) readRegexCache.clear();
|
|
2256
|
+
re = new RegExp(readSource(pattern, root, home));
|
|
2257
|
+
readRegexCache.set(key, re);
|
|
2111
2258
|
}
|
|
2112
2259
|
return re;
|
|
2113
2260
|
}
|
|
2114
|
-
function
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2261
|
+
function defaultRoot() {
|
|
2262
|
+
return process.cwd();
|
|
2263
|
+
}
|
|
2264
|
+
function needleOf(pattern) {
|
|
2265
|
+
const p = pattern.endsWith("/") && pattern.length > 1 ? pattern.slice(0, -1) : pattern;
|
|
2266
|
+
if (p.includes("..")) return "";
|
|
2267
|
+
let tail;
|
|
2268
|
+
if (p.startsWith("**") || !p.includes("/")) tail = p;
|
|
2269
|
+
else if (p.startsWith("//")) tail = posix.normalize(p.slice(1));
|
|
2270
|
+
else if (p === "~" || p.startsWith("~/")) tail = posix.normalize(p.slice(1) || "/");
|
|
2271
|
+
else if (p.startsWith("./")) tail = posix.normalize(p.slice(1));
|
|
2272
|
+
else tail = posix.normalize(p.startsWith("/") ? p : `/${p}`);
|
|
2273
|
+
let best = "";
|
|
2274
|
+
for (const piece of tail.split(/[*?]/)) if (piece.length > best.length) best = piece;
|
|
2275
|
+
return best;
|
|
2276
|
+
}
|
|
2277
|
+
var listCache = /* @__PURE__ */ new WeakMap();
|
|
2278
|
+
function readMatcher(rules, root) {
|
|
2279
|
+
const r = root ?? defaultRoot();
|
|
2280
|
+
const home = process.env.HOME ?? "";
|
|
2281
|
+
const key = `${r}\0${home}\0${rules.length}`;
|
|
2282
|
+
const hit = listCache.get(rules);
|
|
2283
|
+
if (hit && hit.key === key) return hit.matcher;
|
|
2284
|
+
const compiled = rules.filter((x) => x.tool === "Read").map(
|
|
2285
|
+
(rule) => rule.pattern === void 0 ? { rule, needle: "", re: /^/ } : { rule, needle: needleOf(rule.pattern), re: readRegex(rule.pattern, r, home) }
|
|
2286
|
+
);
|
|
2287
|
+
const which = (p) => {
|
|
2288
|
+
for (const c of compiled) {
|
|
2289
|
+
if (c.needle && !p.includes(c.needle)) continue;
|
|
2290
|
+
if (c.re.test(p)) return c.rule;
|
|
2291
|
+
}
|
|
2292
|
+
return void 0;
|
|
2293
|
+
};
|
|
2294
|
+
const matcher = { test: (p) => which(p) !== void 0, which };
|
|
2295
|
+
listCache.set(rules, { key, matcher });
|
|
2296
|
+
return matcher;
|
|
2120
2297
|
}
|
|
2121
2298
|
|
|
2122
2299
|
// src/permissions/split.ts
|
|
@@ -2142,15 +2319,15 @@ function stamp(file2) {
|
|
|
2142
2319
|
var cache = /* @__PURE__ */ new Map();
|
|
2143
2320
|
function loadPermissionRules(root, extraDeny = []) {
|
|
2144
2321
|
const files = [
|
|
2145
|
-
join3(
|
|
2322
|
+
join3(claudeConfigDir(), "settings.json"),
|
|
2146
2323
|
join3(root, ".claude", "settings.json"),
|
|
2147
2324
|
join3(root, ".claude", "settings.local.json")
|
|
2148
2325
|
];
|
|
2149
|
-
const key =
|
|
2326
|
+
const key = [...files, ...extraDeny].join("\0");
|
|
2150
2327
|
const stamps = files.map(stamp);
|
|
2151
2328
|
const hit = cache.get(key);
|
|
2152
2329
|
if (hit?.stamps.every((s, i) => s === stamps[i])) return hit.rules;
|
|
2153
|
-
const out = { allow: [], deny: [], ask: [] };
|
|
2330
|
+
const out = { allow: [], deny: [], ask: [], root };
|
|
2154
2331
|
const push = (list, raws) => {
|
|
2155
2332
|
for (const raw of raws ?? []) {
|
|
2156
2333
|
const r = parseRule(raw);
|
|
@@ -2170,14 +2347,21 @@ function loadPermissionRules(root, extraDeny = []) {
|
|
|
2170
2347
|
}
|
|
2171
2348
|
function checkRead(rules, absPaths) {
|
|
2172
2349
|
for (const kind of ["deny", "ask"]) {
|
|
2173
|
-
|
|
2174
|
-
|
|
2350
|
+
const m = readMatcher(rules[kind], rules.root);
|
|
2351
|
+
for (const p of absPaths) {
|
|
2352
|
+
const r = m.which(p);
|
|
2353
|
+
if (r) return { allowed: false, rule: r.raw, kind };
|
|
2175
2354
|
}
|
|
2176
2355
|
}
|
|
2177
2356
|
return { allowed: true };
|
|
2178
2357
|
}
|
|
2179
2358
|
function checkReadPath(rules, abs) {
|
|
2180
|
-
|
|
2359
|
+
const asWritten = checkRead(rules, [abs]);
|
|
2360
|
+
if (!asWritten.allowed) return asWritten;
|
|
2361
|
+
const real = realpathSync(abs);
|
|
2362
|
+
const native = realpathSync.native(abs);
|
|
2363
|
+
const more = [real, native].filter((p, i, a) => p !== abs && a.indexOf(p) === i);
|
|
2364
|
+
return more.length ? checkRead(rules, more) : asWritten;
|
|
2181
2365
|
}
|
|
2182
2366
|
|
|
2183
2367
|
// src/session/routing-stats.ts
|
|
@@ -2246,10 +2430,8 @@ import {
|
|
|
2246
2430
|
readFileSync as readFileSync3,
|
|
2247
2431
|
writeFileSync as writeFileSync3
|
|
2248
2432
|
} from "fs";
|
|
2249
|
-
import { homedir as homedir4 } from "os";
|
|
2250
2433
|
import { dirname as dirname2, join as join5 } from "path";
|
|
2251
|
-
var
|
|
2252
|
-
var pluginsDir = () => join5(claudeDir2(), "plugins");
|
|
2434
|
+
var pluginsDir = () => claudePluginsDir();
|
|
2253
2435
|
|
|
2254
2436
|
// src/hooks/coexist.ts
|
|
2255
2437
|
var cached;
|
|
@@ -2488,7 +2670,7 @@ function fileOperands(stage, cwd) {
|
|
|
2488
2670
|
for (let f of files) {
|
|
2489
2671
|
if (/[*?[\]{}$`\\]/.test(f)) return void 0;
|
|
2490
2672
|
f = f.replace(/^['"]|['"]$/g, "");
|
|
2491
|
-
if (f.startsWith("~/")) f = join7(
|
|
2673
|
+
if (f.startsWith("~/")) f = join7(homedir2(), f.slice(2));
|
|
2492
2674
|
out.push(resolve2(cwd, f));
|
|
2493
2675
|
}
|
|
2494
2676
|
return out;
|
|
@@ -2639,12 +2821,18 @@ function suggest(toolName, input, root, extra = {}) {
|
|
|
2639
2821
|
return {
|
|
2640
2822
|
tool: "em_*",
|
|
2641
2823
|
reason: "subagents should prefer em_execute / em_batch_execute / em_search over Bash, Read and Grep",
|
|
2642
|
-
prefixPrompt:
|
|
2824
|
+
prefixPrompt: subagentPrefix(true)
|
|
2643
2825
|
};
|
|
2644
2826
|
default:
|
|
2645
2827
|
return void 0;
|
|
2646
2828
|
}
|
|
2647
2829
|
}
|
|
2830
|
+
function subagentPrefix(shell) {
|
|
2831
|
+
const runners = shell ? "em_execute, em_batch_execute, em_execute_file, " : "";
|
|
2832
|
+
return `Use the eminiar tools (${runners}em_fetch_and_index, em_search, em_symbols, em_history) instead of ${shell ? "Bash/" : ""}Read/Grep/WebFetch whenever output may be large; return only derived answers. Check em_recall {query} for project decisions and constraints before proposing changes, em_history {action:"file", path} before editing a file, em_symbols / em_search {source:"code"} before grepping for an identifier, and em_docs {action:"lookup", query} before WebFetch/WebSearch for anything the project depends on.
|
|
2833
|
+
|
|
2834
|
+
`;
|
|
2835
|
+
}
|
|
2648
2836
|
function projectRel(ctx, fp) {
|
|
2649
2837
|
if (!fp) return void 0;
|
|
2650
2838
|
let abs = resolve2(ctx.root, fp);
|
|
@@ -2773,7 +2961,7 @@ var preToolUse = async (input) => {
|
|
|
2773
2961
|
const out = await route(ctx, input, spawn?.kind === "worker");
|
|
2774
2962
|
if (!ctx.config.adjudicator.enabled || input.client === "cursor") return out;
|
|
2775
2963
|
try {
|
|
2776
|
-
const { afterRouting } = await import("./hook-hooks-
|
|
2964
|
+
const { afterRouting } = await import("./hook-hooks-5U2AI6W7.js");
|
|
2777
2965
|
return await afterRouting(ctx, input, spawn, out);
|
|
2778
2966
|
} catch {
|
|
2779
2967
|
return out;
|
|
@@ -2805,15 +2993,16 @@ async function route(ctx, input, swarmWorker) {
|
|
|
2805
2993
|
}
|
|
2806
2994
|
if (mode === "enforce") {
|
|
2807
2995
|
if (s.prefixPrompt) {
|
|
2996
|
+
const prefix = swarmWorker && workerLacksTool(input, spawnType(input), "em_execute") ? subagentPrefix(false) : s.prefixPrompt;
|
|
2808
2997
|
const prompt = String(input.tool_input.prompt ?? "");
|
|
2809
|
-
if (prompt.startsWith(
|
|
2998
|
+
if (prompt.startsWith(prefix)) return void 0;
|
|
2810
2999
|
return {
|
|
2811
3000
|
hookSpecificOutput: {
|
|
2812
3001
|
hookEventName: "PreToolUse",
|
|
2813
3002
|
permissionDecision: "allow",
|
|
2814
3003
|
updatedInput: {
|
|
2815
3004
|
...input.tool_input,
|
|
2816
|
-
prompt:
|
|
3005
|
+
prompt: prefix + (swarmWorker ? "" : await briefFor(ctx, prompt)) + prompt
|
|
2817
3006
|
}
|
|
2818
3007
|
}
|
|
2819
3008
|
};
|
|
@@ -2832,6 +3021,10 @@ async function route(ctx, input, swarmWorker) {
|
|
|
2832
3021
|
hint
|
|
2833
3022
|
);
|
|
2834
3023
|
}
|
|
3024
|
+
if (input.agent_id && workerLacksTool(input, input.agent_type, s.tool)) {
|
|
3025
|
+
record("allow", "no-shell");
|
|
3026
|
+
return withContext(void 0, hint);
|
|
3027
|
+
}
|
|
2835
3028
|
record("deny");
|
|
2836
3029
|
hookLog("info", `deny ${input.tool_name} \u2192 ${s.tool} [${s.rule ?? "?"}]: ${callHead(input)}`);
|
|
2837
3030
|
return {
|
|
@@ -2954,18 +3147,22 @@ var sessionStart = async (input) => {
|
|
|
2954
3147
|
const sessionId = input.session_id || "unknown";
|
|
2955
3148
|
setCurrentSession(ctx, sessionId);
|
|
2956
3149
|
const db = getStorage(ctx, { vectors: false });
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
3150
|
+
try {
|
|
3151
|
+
db.sessionUpsert({
|
|
3152
|
+
id: sessionId,
|
|
3153
|
+
project_root: ctx.root,
|
|
3154
|
+
source: input.source,
|
|
3155
|
+
transcript_path: input.transcript_path
|
|
3156
|
+
});
|
|
3157
|
+
db.eventInsert({
|
|
3158
|
+
sessionId,
|
|
3159
|
+
category: "session_start",
|
|
3160
|
+
priority: 4,
|
|
3161
|
+
payload: { source: input.source }
|
|
3162
|
+
});
|
|
3163
|
+
} catch (err) {
|
|
3164
|
+
hookLog("info", `SessionStart: session not recorded: ${err.message}`);
|
|
3165
|
+
}
|
|
2969
3166
|
if (process.env.CLAUDE_ENV_FILE) {
|
|
2970
3167
|
try {
|
|
2971
3168
|
appendFileSync(
|
|
@@ -2984,7 +3181,11 @@ export EMINIAR_PROJECT_ROOT=${JSON.stringify(ctx.root)}
|
|
|
2984
3181
|
if (snap) {
|
|
2985
3182
|
parts.push(`## Session Guide (eminiar)
|
|
2986
3183
|
${snap.body}`);
|
|
2987
|
-
|
|
3184
|
+
try {
|
|
3185
|
+
db.snapshotMarkConsumed(snap.id);
|
|
3186
|
+
} catch (err) {
|
|
3187
|
+
hookLog("info", `SessionStart: snapshot not marked consumed: ${err.message}`);
|
|
3188
|
+
}
|
|
2988
3189
|
} else {
|
|
2989
3190
|
const prev = input.source === "resume" ? db.sessionLatest() : void 0;
|
|
2990
3191
|
const guide = renderGuide(db, prev && prev.id !== sessionId ? prev.id : sessionId, 2048);
|
|
@@ -2997,7 +3198,7 @@ ${guide}`);
|
|
|
2997
3198
|
if (swarm) parts.push(swarm);
|
|
2998
3199
|
if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
|
|
2999
3200
|
try {
|
|
3000
|
-
const { onSessionStart } = await import("./hook-hooks-
|
|
3201
|
+
const { onSessionStart } = await import("./hook-hooks-5U2AI6W7.js");
|
|
3001
3202
|
const line2 = onSessionStart(ctx, db, input);
|
|
3002
3203
|
if (line2) parts.push(line2);
|
|
3003
3204
|
} catch {
|
|
@@ -3072,7 +3273,29 @@ var handlers = {
|
|
|
3072
3273
|
};
|
|
3073
3274
|
|
|
3074
3275
|
// src/hooks/dispatch.ts
|
|
3075
|
-
|
|
3276
|
+
var late = false;
|
|
3277
|
+
function ranLate() {
|
|
3278
|
+
return late;
|
|
3279
|
+
}
|
|
3280
|
+
async function runWithin(handler, input, ms) {
|
|
3281
|
+
let timer;
|
|
3282
|
+
const deadline = new Promise((resolve3) => {
|
|
3283
|
+
timer = setTimeout(() => resolve3("late"), ms);
|
|
3284
|
+
});
|
|
3285
|
+
try {
|
|
3286
|
+
const r = await Promise.race([handler(input), deadline]);
|
|
3287
|
+
if (r === "late") {
|
|
3288
|
+
late = true;
|
|
3289
|
+
return { out: void 0, timedOut: true };
|
|
3290
|
+
}
|
|
3291
|
+
return { out: r, timedOut: false };
|
|
3292
|
+
} finally {
|
|
3293
|
+
clearTimeout(timer);
|
|
3294
|
+
if (!late) clearBudget();
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
async function dispatch(raw, argv, opts = {}) {
|
|
3298
|
+
late = false;
|
|
3076
3299
|
let parsed;
|
|
3077
3300
|
try {
|
|
3078
3301
|
parsed = raw.trim() ? JSON.parse(raw) : {};
|
|
@@ -3083,13 +3306,20 @@ async function dispatch(raw, argv) {
|
|
|
3083
3306
|
const forced = argv.indexOf("--event");
|
|
3084
3307
|
if (forced !== -1 && argv[forced + 1]) parsed.hook_event_name = argv[forced + 1];
|
|
3085
3308
|
if (argv.includes("--selftest")) return JSON.stringify({ systemMessage: "eminiar hook ok" });
|
|
3086
|
-
if (detectClient(parsed, argv) === "cursor") return runCursor(parsed);
|
|
3309
|
+
if (detectClient(parsed, argv) === "cursor") return runCursor(parsed, opts.budgetMs);
|
|
3087
3310
|
const input = parsed;
|
|
3088
3311
|
const handler = handlers[input.hook_event_name];
|
|
3089
3312
|
if (!handler) return "{}";
|
|
3313
|
+
const ms = opts.budgetMs ?? budgetFor(input.hook_event_name);
|
|
3314
|
+
startBudget(ms);
|
|
3090
3315
|
try {
|
|
3091
|
-
const
|
|
3092
|
-
|
|
3316
|
+
const r = await runWithin(handler, input, ms);
|
|
3317
|
+
if (r.timedOut) {
|
|
3318
|
+
const err = new Error(`hook budget of ${ms} ms exceeded`);
|
|
3319
|
+
hookLog("error", `${input.hook_event_name}: ${err.message}; answered without it`);
|
|
3320
|
+
return failClosed(input, err) ?? "{}";
|
|
3321
|
+
}
|
|
3322
|
+
return JSON.stringify(r.out ?? {});
|
|
3093
3323
|
} catch (err) {
|
|
3094
3324
|
hookLog("error", `${input.hook_event_name}: ${err.stack ?? String(err)}`);
|
|
3095
3325
|
return failClosed(input, err) ?? "{}";
|
|
@@ -3104,6 +3334,10 @@ function failClosed(input, err) {
|
|
|
3104
3334
|
try {
|
|
3105
3335
|
if (input.hook_event_name !== "PreToolUse" || input.client === "cursor" || input.tool_name !== "Agent" && input.tool_name !== "Task" || !rawEnforced({ cwd: input.cwd }))
|
|
3106
3336
|
return void 0;
|
|
3337
|
+
try {
|
|
3338
|
+
swarmAgentReturned(getContext(input.cwd), input);
|
|
3339
|
+
} catch {
|
|
3340
|
+
}
|
|
3107
3341
|
return JSON.stringify({
|
|
3108
3342
|
hookSpecificOutput: {
|
|
3109
3343
|
hookEventName: "PreToolUse",
|
|
@@ -3115,14 +3349,18 @@ function failClosed(input, err) {
|
|
|
3115
3349
|
return void 0;
|
|
3116
3350
|
}
|
|
3117
3351
|
}
|
|
3118
|
-
async function runCursor(parsed) {
|
|
3352
|
+
async function runCursor(parsed, budgetMs2) {
|
|
3119
3353
|
const input = fromCursor(parsed);
|
|
3120
3354
|
const event = input.hook_event_name;
|
|
3121
3355
|
let out;
|
|
3122
3356
|
const handler = handlers[event];
|
|
3123
3357
|
if (handler) {
|
|
3358
|
+
const ms = budgetMs2 ?? budgetFor(event);
|
|
3359
|
+
startBudget(ms);
|
|
3124
3360
|
try {
|
|
3125
|
-
|
|
3361
|
+
const r = await runWithin(handler, input, ms);
|
|
3362
|
+
if (r.timedOut) hookLog("error", `cursor ${event}: hook budget of ${ms} ms exceeded`);
|
|
3363
|
+
out = r.out;
|
|
3126
3364
|
} catch (err) {
|
|
3127
3365
|
hookLog("error", `cursor ${event}: ${err.stack ?? String(err)}`);
|
|
3128
3366
|
}
|
|
@@ -3153,12 +3391,18 @@ async function main() {
|
|
|
3153
3391
|
return;
|
|
3154
3392
|
}
|
|
3155
3393
|
if (process.argv.includes("--adjudicate")) {
|
|
3156
|
-
const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-
|
|
3394
|
+
const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-3TLK23ZY.js");
|
|
3157
3395
|
setTimeout(() => process.exit(0), ASYNC_JOB_MAX_MS).unref();
|
|
3158
3396
|
await runAsyncJob(raw, { log: (line2) => hookLog("info", line2) });
|
|
3159
3397
|
return;
|
|
3160
3398
|
}
|
|
3161
|
-
|
|
3399
|
+
setStorageBusyTimeout(lockWaitFor(raw, process.argv.slice(2)));
|
|
3400
|
+
const out = await dispatch(raw, process.argv.slice(2));
|
|
3401
|
+
if (!ranLate()) {
|
|
3402
|
+
process.stdout.write(out);
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
process.stdout.write(out, () => process.exit(0));
|
|
3162
3406
|
}
|
|
3163
3407
|
main().catch((err) => {
|
|
3164
3408
|
hookLog("error", `fatal: ${err.stack ?? String(err)}`);
|