@zhijiewang/openharness 2.20.0 → 2.22.0

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.
@@ -10,6 +10,7 @@
10
10
  * - prompt: LLM yes/no check via provider.complete()
11
11
  */
12
12
  import { spawn, spawnSync } from "node:child_process";
13
+ import { debug } from "../utils/debug.js";
13
14
  import { readOhConfig } from "./config.js";
14
15
  let cachedHooks;
15
16
  export function getHooks() {
@@ -22,6 +23,18 @@ export function getHooks() {
22
23
  /** Clear hook cache (call after config changes) */
23
24
  export function invalidateHookCache() {
24
25
  cachedHooks = undefined;
26
+ cachedDisableAllHooks = undefined;
27
+ }
28
+ let cachedDisableAllHooks;
29
+ /**
30
+ * Whether the configured `disableAllHooks` kill switch is set.
31
+ * Cached so the per-emit cost is a single boolean read.
32
+ */
33
+ export function areHooksEnabled() {
34
+ if (cachedDisableAllHooks === undefined) {
35
+ cachedDisableAllHooks = readOhConfig()?.disableAllHooks === true;
36
+ }
37
+ return !cachedDisableAllHooks;
25
38
  }
26
39
  function buildEnv(event, ctx) {
27
40
  const env = {
@@ -71,6 +84,22 @@ function buildEnv(event, ctx) {
71
84
  env.OH_TURN_NUMBER = ctx.turnNumber;
72
85
  if (ctx.turnReason !== undefined)
73
86
  env.OH_TURN_REASON = ctx.turnReason;
87
+ if (ctx.worktreePath !== undefined)
88
+ env.OH_WORKTREE_PATH = ctx.worktreePath;
89
+ if (ctx.worktreeParent !== undefined)
90
+ env.OH_WORKTREE_PARENT = ctx.worktreeParent;
91
+ if (ctx.worktreeForced !== undefined)
92
+ env.OH_WORKTREE_FORCED = ctx.worktreeForced;
93
+ if (ctx.elicitationServer !== undefined)
94
+ env.OH_ELICITATION_SERVER = ctx.elicitationServer;
95
+ if (ctx.elicitationMessage !== undefined)
96
+ env.OH_ELICITATION_MESSAGE = ctx.elicitationMessage;
97
+ if (ctx.elicitationSchema !== undefined)
98
+ env.OH_ELICITATION_SCHEMA = ctx.elicitationSchema;
99
+ if (ctx.elicitationAction !== undefined)
100
+ env.OH_ELICITATION_ACTION = ctx.elicitationAction;
101
+ if (ctx.elicitationContent !== undefined)
102
+ env.OH_ELICITATION_CONTENT = ctx.elicitationContent;
74
103
  return env;
75
104
  }
76
105
  /**
@@ -400,10 +429,14 @@ async function executeHookDef(def, event, ctx) {
400
429
  * All other hooks run asynchronously to avoid blocking the event loop.
401
430
  */
402
431
  export function emitHook(event, ctx = {}) {
432
+ if (!areHooksEnabled())
433
+ return true;
403
434
  const hooks = getHooks();
404
435
  if (!hooks)
405
436
  return true;
406
437
  const defs = hooks[event] ?? [];
438
+ if (defs.length > 0)
439
+ debug("hooks", "fire", { event, count: defs.length, tool: ctx.toolName });
407
440
  const env = buildEnv(event, ctx);
408
441
  if (event === "preToolUse") {
409
442
  // preToolUse command hooks must be synchronous — they gate tool execution
@@ -456,6 +489,8 @@ export function emitHook(event, ctx = {}) {
456
489
  * Supports all hook types (command, HTTP, prompt).
457
490
  */
458
491
  export async function emitHookAsync(event, ctx = {}) {
492
+ if (!areHooksEnabled())
493
+ return true;
459
494
  const hooks = getHooks();
460
495
  if (!hooks)
461
496
  return true;
@@ -570,6 +605,8 @@ async function runHookForOutcome(def, event, ctx) {
570
605
  * from hooks is ignored — outcome.allowed is always true. additionalContext is still collected.
571
606
  */
572
607
  export async function emitHookWithOutcome(event, ctx = {}) {
608
+ if (!areHooksEnabled())
609
+ return { allowed: true };
573
610
  const hooks = getHooks();
574
611
  const list = hooks?.[event];
575
612
  if (!list || list.length === 0)