eminiar 0.13.0 → 0.13.1

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-DSLVDGOB.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-BECSRJFD.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-IDTGXCFD.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-IDTGXCFD.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-IDTGXCFD.js");
1950
2068
  onSessionEnd(ctx, db, input);
1951
2069
  } catch {
1952
2070
  }
@@ -2080,13 +2198,12 @@ 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
@@ -2142,11 +2259,11 @@ function stamp(file2) {
2142
2259
  var cache = /* @__PURE__ */ new Map();
2143
2260
  function loadPermissionRules(root, extraDeny = []) {
2144
2261
  const files = [
2145
- join3(homedir3(), ".claude", "settings.json"),
2262
+ join3(claudeConfigDir(), "settings.json"),
2146
2263
  join3(root, ".claude", "settings.json"),
2147
2264
  join3(root, ".claude", "settings.local.json")
2148
2265
  ];
2149
- const key = `${root}\0${extraDeny.join("\0")}`;
2266
+ const key = [...files, ...extraDeny].join("\0");
2150
2267
  const stamps = files.map(stamp);
2151
2268
  const hit = cache.get(key);
2152
2269
  if (hit?.stamps.every((s, i) => s === stamps[i])) return hit.rules;
@@ -2246,10 +2363,8 @@ import {
2246
2363
  readFileSync as readFileSync3,
2247
2364
  writeFileSync as writeFileSync3
2248
2365
  } from "fs";
2249
- import { homedir as homedir4 } from "os";
2250
2366
  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");
2367
+ var pluginsDir = () => claudePluginsDir();
2253
2368
 
2254
2369
  // src/hooks/coexist.ts
2255
2370
  var cached;
@@ -2488,7 +2603,7 @@ function fileOperands(stage, cwd) {
2488
2603
  for (let f of files) {
2489
2604
  if (/[*?[\]{}$`\\]/.test(f)) return void 0;
2490
2605
  f = f.replace(/^['"]|['"]$/g, "");
2491
- if (f.startsWith("~/")) f = join7(homedir5(), f.slice(2));
2606
+ if (f.startsWith("~/")) f = join7(homedir2(), f.slice(2));
2492
2607
  out.push(resolve2(cwd, f));
2493
2608
  }
2494
2609
  return out;
@@ -2639,12 +2754,18 @@ function suggest(toolName, input, root, extra = {}) {
2639
2754
  return {
2640
2755
  tool: "em_*",
2641
2756
  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'
2757
+ prefixPrompt: subagentPrefix(true)
2643
2758
  };
2644
2759
  default:
2645
2760
  return void 0;
2646
2761
  }
2647
2762
  }
2763
+ function subagentPrefix(shell) {
2764
+ const runners = shell ? "em_execute, em_batch_execute, em_execute_file, " : "";
2765
+ 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.
2766
+
2767
+ `;
2768
+ }
2648
2769
  function projectRel(ctx, fp) {
2649
2770
  if (!fp) return void 0;
2650
2771
  let abs = resolve2(ctx.root, fp);
@@ -2773,7 +2894,7 @@ var preToolUse = async (input) => {
2773
2894
  const out = await route(ctx, input, spawn?.kind === "worker");
2774
2895
  if (!ctx.config.adjudicator.enabled || input.client === "cursor") return out;
2775
2896
  try {
2776
- const { afterRouting } = await import("./hook-hooks-U6GDOQSY.js");
2897
+ const { afterRouting } = await import("./hook-hooks-IDTGXCFD.js");
2777
2898
  return await afterRouting(ctx, input, spawn, out);
2778
2899
  } catch {
2779
2900
  return out;
@@ -2805,15 +2926,16 @@ async function route(ctx, input, swarmWorker) {
2805
2926
  }
2806
2927
  if (mode === "enforce") {
2807
2928
  if (s.prefixPrompt) {
2929
+ const prefix = swarmWorker && workerLacksTool(input, spawnType(input), "em_execute") ? subagentPrefix(false) : s.prefixPrompt;
2808
2930
  const prompt = String(input.tool_input.prompt ?? "");
2809
- if (prompt.startsWith(s.prefixPrompt)) return void 0;
2931
+ if (prompt.startsWith(prefix)) return void 0;
2810
2932
  return {
2811
2933
  hookSpecificOutput: {
2812
2934
  hookEventName: "PreToolUse",
2813
2935
  permissionDecision: "allow",
2814
2936
  updatedInput: {
2815
2937
  ...input.tool_input,
2816
- prompt: s.prefixPrompt + (swarmWorker ? "" : await briefFor(ctx, prompt)) + prompt
2938
+ prompt: prefix + (swarmWorker ? "" : await briefFor(ctx, prompt)) + prompt
2817
2939
  }
2818
2940
  }
2819
2941
  };
@@ -2832,6 +2954,10 @@ async function route(ctx, input, swarmWorker) {
2832
2954
  hint
2833
2955
  );
2834
2956
  }
2957
+ if (input.agent_id && workerLacksTool(input, input.agent_type, s.tool)) {
2958
+ record("allow", "no-shell");
2959
+ return withContext(void 0, hint);
2960
+ }
2835
2961
  record("deny");
2836
2962
  hookLog("info", `deny ${input.tool_name} \u2192 ${s.tool} [${s.rule ?? "?"}]: ${callHead(input)}`);
2837
2963
  return {
@@ -2954,18 +3080,22 @@ var sessionStart = async (input) => {
2954
3080
  const sessionId = input.session_id || "unknown";
2955
3081
  setCurrentSession(ctx, sessionId);
2956
3082
  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
- });
3083
+ try {
3084
+ db.sessionUpsert({
3085
+ id: sessionId,
3086
+ project_root: ctx.root,
3087
+ source: input.source,
3088
+ transcript_path: input.transcript_path
3089
+ });
3090
+ db.eventInsert({
3091
+ sessionId,
3092
+ category: "session_start",
3093
+ priority: 4,
3094
+ payload: { source: input.source }
3095
+ });
3096
+ } catch (err) {
3097
+ hookLog("info", `SessionStart: session not recorded: ${err.message}`);
3098
+ }
2969
3099
  if (process.env.CLAUDE_ENV_FILE) {
2970
3100
  try {
2971
3101
  appendFileSync(
@@ -2984,7 +3114,11 @@ export EMINIAR_PROJECT_ROOT=${JSON.stringify(ctx.root)}
2984
3114
  if (snap) {
2985
3115
  parts.push(`## Session Guide (eminiar)
2986
3116
  ${snap.body}`);
2987
- db.snapshotMarkConsumed(snap.id);
3117
+ try {
3118
+ db.snapshotMarkConsumed(snap.id);
3119
+ } catch (err) {
3120
+ hookLog("info", `SessionStart: snapshot not marked consumed: ${err.message}`);
3121
+ }
2988
3122
  } else {
2989
3123
  const prev = input.source === "resume" ? db.sessionLatest() : void 0;
2990
3124
  const guide = renderGuide(db, prev && prev.id !== sessionId ? prev.id : sessionId, 2048);
@@ -2997,7 +3131,7 @@ ${guide}`);
2997
3131
  if (swarm) parts.push(swarm);
2998
3132
  if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
2999
3133
  try {
3000
- const { onSessionStart } = await import("./hook-hooks-U6GDOQSY.js");
3134
+ const { onSessionStart } = await import("./hook-hooks-IDTGXCFD.js");
3001
3135
  const line2 = onSessionStart(ctx, db, input);
3002
3136
  if (line2) parts.push(line2);
3003
3137
  } catch {
@@ -3072,7 +3206,29 @@ var handlers = {
3072
3206
  };
3073
3207
 
3074
3208
  // src/hooks/dispatch.ts
3075
- async function dispatch(raw, argv) {
3209
+ var late = false;
3210
+ function ranLate() {
3211
+ return late;
3212
+ }
3213
+ async function runWithin(handler, input, ms) {
3214
+ let timer;
3215
+ const deadline = new Promise((resolve3) => {
3216
+ timer = setTimeout(() => resolve3("late"), ms);
3217
+ });
3218
+ try {
3219
+ const r = await Promise.race([handler(input), deadline]);
3220
+ if (r === "late") {
3221
+ late = true;
3222
+ return { out: void 0, timedOut: true };
3223
+ }
3224
+ return { out: r, timedOut: false };
3225
+ } finally {
3226
+ clearTimeout(timer);
3227
+ if (!late) clearBudget();
3228
+ }
3229
+ }
3230
+ async function dispatch(raw, argv, opts = {}) {
3231
+ late = false;
3076
3232
  let parsed;
3077
3233
  try {
3078
3234
  parsed = raw.trim() ? JSON.parse(raw) : {};
@@ -3083,13 +3239,20 @@ async function dispatch(raw, argv) {
3083
3239
  const forced = argv.indexOf("--event");
3084
3240
  if (forced !== -1 && argv[forced + 1]) parsed.hook_event_name = argv[forced + 1];
3085
3241
  if (argv.includes("--selftest")) return JSON.stringify({ systemMessage: "eminiar hook ok" });
3086
- if (detectClient(parsed, argv) === "cursor") return runCursor(parsed);
3242
+ if (detectClient(parsed, argv) === "cursor") return runCursor(parsed, opts.budgetMs);
3087
3243
  const input = parsed;
3088
3244
  const handler = handlers[input.hook_event_name];
3089
3245
  if (!handler) return "{}";
3246
+ const ms = opts.budgetMs ?? budgetFor(input.hook_event_name);
3247
+ startBudget(ms);
3090
3248
  try {
3091
- const out = await handler(input);
3092
- return JSON.stringify(out ?? {});
3249
+ const r = await runWithin(handler, input, ms);
3250
+ if (r.timedOut) {
3251
+ const err = new Error(`hook budget of ${ms} ms exceeded`);
3252
+ hookLog("error", `${input.hook_event_name}: ${err.message}; answered without it`);
3253
+ return failClosed(input, err) ?? "{}";
3254
+ }
3255
+ return JSON.stringify(r.out ?? {});
3093
3256
  } catch (err) {
3094
3257
  hookLog("error", `${input.hook_event_name}: ${err.stack ?? String(err)}`);
3095
3258
  return failClosed(input, err) ?? "{}";
@@ -3104,6 +3267,10 @@ function failClosed(input, err) {
3104
3267
  try {
3105
3268
  if (input.hook_event_name !== "PreToolUse" || input.client === "cursor" || input.tool_name !== "Agent" && input.tool_name !== "Task" || !rawEnforced({ cwd: input.cwd }))
3106
3269
  return void 0;
3270
+ try {
3271
+ swarmAgentReturned(getContext(input.cwd), input);
3272
+ } catch {
3273
+ }
3107
3274
  return JSON.stringify({
3108
3275
  hookSpecificOutput: {
3109
3276
  hookEventName: "PreToolUse",
@@ -3115,14 +3282,18 @@ function failClosed(input, err) {
3115
3282
  return void 0;
3116
3283
  }
3117
3284
  }
3118
- async function runCursor(parsed) {
3285
+ async function runCursor(parsed, budgetMs2) {
3119
3286
  const input = fromCursor(parsed);
3120
3287
  const event = input.hook_event_name;
3121
3288
  let out;
3122
3289
  const handler = handlers[event];
3123
3290
  if (handler) {
3291
+ const ms = budgetMs2 ?? budgetFor(event);
3292
+ startBudget(ms);
3124
3293
  try {
3125
- out = await handler(input);
3294
+ const r = await runWithin(handler, input, ms);
3295
+ if (r.timedOut) hookLog("error", `cursor ${event}: hook budget of ${ms} ms exceeded`);
3296
+ out = r.out;
3126
3297
  } catch (err) {
3127
3298
  hookLog("error", `cursor ${event}: ${err.stack ?? String(err)}`);
3128
3299
  }
@@ -3153,12 +3324,18 @@ async function main() {
3153
3324
  return;
3154
3325
  }
3155
3326
  if (process.argv.includes("--adjudicate")) {
3156
- const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-4MIP3VHX.js");
3327
+ const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-J3UKDO62.js");
3157
3328
  setTimeout(() => process.exit(0), ASYNC_JOB_MAX_MS).unref();
3158
3329
  await runAsyncJob(raw, { log: (line2) => hookLog("info", line2) });
3159
3330
  return;
3160
3331
  }
3161
- process.stdout.write(await dispatch(raw, process.argv.slice(2)));
3332
+ setStorageBusyTimeout(lockWaitFor(raw, process.argv.slice(2)));
3333
+ const out = await dispatch(raw, process.argv.slice(2));
3334
+ if (!ranLate()) {
3335
+ process.stdout.write(out);
3336
+ return;
3337
+ }
3338
+ process.stdout.write(out, () => process.exit(0));
3162
3339
  }
3163
3340
  main().catch((err) => {
3164
3341
  hookLog("error", `fatal: ${err.stack ?? String(err)}`);