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/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
- } from "./hook-chunk-MVOBVFQL.js";
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-IYHVLMJ4.js";
55
+ } from "./hook-chunk-ETYHCPPH.js";
51
56
 
52
57
  // src/hook.ts
53
58
  init_esm_shims();
54
59
 
55
- // src/hooks/dispatch.ts
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 || join2(homedir2(), ".claude");
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-U6GDOQSY.js");
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-U6GDOQSY.js");
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
- if (input.agent_id) swarmActivity(getContext(input.cwd), input, true);
1884
- if (input.tool_name === "Agent" || input.tool_name === "Task")
1885
- swarmAgentReturned(getContext(input.cwd), input);
1886
- const { db, root, sessionId } = open(input);
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 } = getContext(input.cwd).config;
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-U6GDOQSY.js");
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 homedir5 } from "os";
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
- let re = readRegexCache.get(pattern);
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 >= 1024) readRegexCache.clear();
2107
- re = new RegExp(
2108
- `^${pattern.split("**").map((seg) => seg.split("*").map(escapeRe).join("[^/]*")).join(".*")}$`
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 matchRead(rule, absPath) {
2115
- if (rule.tool !== "Read") return false;
2116
- if (rule.pattern === void 0) return true;
2117
- let p = rule.pattern;
2118
- if (p.startsWith("~")) p = (process.env.HOME ?? "") + p.slice(1);
2119
- return readRegex(p).test(absPath);
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(homedir3(), ".claude", "settings.json"),
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 = `${root}\0${extraDeny.join("\0")}`;
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
- for (const r of rules[kind]) {
2174
- if (absPaths.some((p) => matchRead(r, p))) return { allowed: false, rule: r.raw, kind };
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
- return checkRead(rules, [.../* @__PURE__ */ new Set([abs, realpathSync(abs), realpathSync.native(abs)])]);
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 claudeDir2 = () => process.env.CLAUDE_CONFIG_DIR || join5(homedir4(), ".claude");
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(homedir5(), f.slice(2));
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: 'Use the eminiar tools (em_execute, em_batch_execute, em_execute_file, em_fetch_and_index, em_search, em_symbols, em_history) instead of 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.\n\n'
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-U6GDOQSY.js");
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(s.prefixPrompt)) return void 0;
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: s.prefixPrompt + (swarmWorker ? "" : await briefFor(ctx, prompt)) + 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
- db.sessionUpsert({
2958
- id: sessionId,
2959
- project_root: ctx.root,
2960
- source: input.source,
2961
- transcript_path: input.transcript_path
2962
- });
2963
- db.eventInsert({
2964
- sessionId,
2965
- category: "session_start",
2966
- priority: 4,
2967
- payload: { source: input.source }
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
- db.snapshotMarkConsumed(snap.id);
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-U6GDOQSY.js");
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
- async function dispatch(raw, argv) {
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 out = await handler(input);
3092
- return JSON.stringify(out ?? {});
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
- out = await handler(input);
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-4MIP3VHX.js");
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
- process.stdout.write(await dispatch(raw, process.argv.slice(2)));
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)}`);