@fusengine/harness 0.1.91 → 0.1.92

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.
@@ -90,7 +90,13 @@ export async function dispatchAipilot(event: string, payload: Record<string, unk
90
90
  if (event === "SubagentStop") return onSubagentStop(payload, cwd, home, id);
91
91
  // Stop too: Codex emits no SessionEnd, so its ai-pilot hooks.json wires Stop here as the sole analytics-flush trigger — reusing the SessionEnd handler verbatim (codex-plugins/docs/reference/hooks.md).
92
92
  if (event === "SessionEnd" || event === "Stop") { await cacheAnalyticsSave(home, now); return ""; }
93
- if (event === "PreToolUse") return docCacheGate(payload, cwd, now, home, id);
93
+ // "BeforeMCPExecution" is a Cursor-only lifecycle literal (produced solely
94
+ // by cursorEventContract in adapters/cursor/events.ts); asyncScopeStdout
95
+ // forwards every non-Cursor id's `hook_event_name` RAW and unvalidated, so
96
+ // gating on the literal alone would let a claude-code/codex payload that
97
+ // happens to carry this exact string reach docCacheGate — the guard must
98
+ // be structural (`id === "cursor"`), never event-name-only.
99
+ if (event === "PreToolUse" || (id === "cursor" && event === "BeforeMCPExecution")) return docCacheGate(payload, cwd, now, home, id);
94
100
  return null;
95
101
  }
96
102
 
@@ -5,8 +5,12 @@
5
5
  * matches the failure message — reusing the PreToolUse {@link lessonFor} index and
6
6
  * its cooldown (idempotent under the ~11-process fan-out). Fail-open throughout.
7
7
  *
8
- * Claude-Code-only: no equivalent `PostToolUseFailure` hook exists on Codex or
9
- * Hermes, so this handler is never reached through those adapters.
8
+ * Claude-Code-only in the sense that no equivalent `PostToolUseFailure` hook
9
+ * exists on Codex or Hermes but Cursor's own `postToolUseFailure` DOES
10
+ * arrive here too, via `lifecycle-bridge.ts`'s `lifecycleStdout` translating
11
+ * the wire event name and forwarding to `dispatchLifecycle`'s
12
+ * `"PostToolUseFailure"` case (see `handle.ts`'s `cursorRawPayloadProjection`
13
+ * for how `data.tool_name` below arrives already canonicalized on Cursor).
10
14
  * @packageDocumentation
11
15
  */
12
16
  import { homedir } from "node:os";
@@ -2,8 +2,15 @@
2
2
  * Dynamic rules-plugin root resolution. The historical `CLAUDE_PLUGIN_ROOT ??
3
3
  * cwd` chain only worked when the harness exported the plugin root — Kimi
4
4
  * injects `KIMI_PLUGIN_ROOT` instead, and a bare cwd fallback never held a
5
- * `rules/` dir. Resolution order (first hit wins):
6
- * 1. `CLAUDE_PLUGIN_ROOT` (claude-code/codex plugin-declared hooks);
5
+ * `rules/` dir.
6
+ *
7
+ * `id === "cursor"` is resolved by a SEPARATE branch (`resolveCursorPluginRoot`)
8
+ * before any of the below, because Cursor's env contract differs from the
9
+ * other harnesses (see `../../adapters/cursor/plugin-root.ts`) — it is never
10
+ * folded into the shared switch. For every other id, resolution order (first
11
+ * hit wins, unchanged):
12
+ * 1. `CLAUDE_PLUGIN_ROOT` (read for ALL non-cursor ids, historical quirk —
13
+ * frozen by non-regression tests, do not "fix" without an explicit ask);
7
14
  * 2. `KIMI_PLUGIN_ROOT` (kimi plugin-declared hooks);
8
15
  * 3. Per-harness install probe (claude marketplace, codex versioned cache,
9
16
  * kimi managed plugins) — first `<plugin>/rules` dir whose plugin folder
@@ -14,6 +21,7 @@ import { existsSync, readdirSync } from "node:fs";
14
21
  import { homedir } from "node:os";
15
22
  import { join } from "node:path";
16
23
  import { maxSemver } from "../../util/semver";
24
+ import { resolveCursorPluginRoot } from "../../adapters/cursor/plugin-root";
17
25
 
18
26
  /** Immediate child dir names of `dir`, or [] when unreadable. */
19
27
  function children(dir: string): string[] {
@@ -72,6 +80,14 @@ export function resolveRulesRoot(
72
80
  cwd: string,
73
81
  env: Record<string, string | undefined> = process.env,
74
82
  ): string {
83
+ if (id === "cursor") {
84
+ const result = resolveCursorPluginRoot(env, cwd);
85
+ if (result.root) return result.root;
86
+ process.stderr.write(
87
+ `[fuse-harness] cursor: no plugin root proven (checked: ${result.checked.join("; ")}); rules root falls back to ${cwd}\n`,
88
+ );
89
+ return cwd;
90
+ }
75
91
  if (env.CLAUDE_PLUGIN_ROOT) return env.CLAUDE_PLUGIN_ROOT;
76
92
  if (env.KIMI_PLUGIN_ROOT) return env.KIMI_PLUGIN_ROOT;
77
93
  const home = env.HOME ?? homedir();