@elizaos/plugin-agent-orchestrator 0.3.8 → 0.3.9

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.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Claude Code HTTP Hooks — Webhook Endpoint
3
+ *
4
+ * Receives structured hook events from Claude Code's HTTP hooks system.
5
+ * Replaces fragile PTY output scraping for state detection with deterministic
6
+ * event-driven signals.
7
+ *
8
+ * @module api/hook-routes
9
+ */
10
+ import type { IncomingMessage, ServerResponse } from "node:http";
11
+ import type { RouteContext } from "./routes.js";
12
+ /**
13
+ * Handle Claude Code HTTP hook routes.
14
+ * Returns true if the route was handled, false otherwise.
15
+ */
16
+ export declare function handleHookRoutes(req: IncomingMessage, res: ServerResponse, pathname: string, ctx: RouteContext): Promise<boolean>;
17
+ //# sourceMappingURL=hook-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-routes.d.ts","sourceRoot":"","sources":["../../src/api/hook-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiBhD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,OAAO,CAAC,CAmHlB"}
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAM/E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAGD,eAAO,MAAM,aAAa,QAAc,CAAC;AAGzC,wBAAsB,SAAS,CAC7B,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsBlC;AAGD,wBAAgB,QAAQ,CACtB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,SAAS,EACf,MAAM,SAAM,GACX,IAAI,CAGN;AAGD,wBAAgB,SAAS,CACvB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,SAAM,GACX,IAAI,CAEN;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,aAAa,EACtB,WAAW,CAAC,EAAE,gBAAgB,IAgBtB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,UAAU,MAAM,sBAEpE"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAO/E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAGD,eAAO,MAAM,aAAa,QAAc,CAAC;AAGzC,wBAAsB,SAAS,CAC7B,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsBlC;AAGD,wBAAgB,QAAQ,CACtB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,SAAS,EACf,MAAM,SAAM,GACX,IAAI,CAGN;AAGD,wBAAgB,SAAS,CACvB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,SAAM,GACX,IAAI,CAEN;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,OAAO,CAAC,CA4BlB;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,aAAa,EACtB,WAAW,CAAC,EAAE,gBAAgB,IAgBtB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,UAAU,MAAM,sBAEpE"}
package/dist/index.js CHANGED
@@ -471,6 +471,18 @@ __export(exports_swarm_decision_loop, {
471
471
  });
472
472
  import * as path from "node:path";
473
473
  import { ModelType as ModelType3 } from "@elizaos/core";
474
+ function withTimeout(promise, ms, label) {
475
+ return new Promise((resolve2, reject) => {
476
+ const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
477
+ promise.then((val) => {
478
+ clearTimeout(timer);
479
+ resolve2(val);
480
+ }, (err) => {
481
+ clearTimeout(timer);
482
+ reject(err);
483
+ });
484
+ });
485
+ }
474
486
  function toContextSummary(taskCtx) {
475
487
  return {
476
488
  sessionId: taskCtx.sessionId,
@@ -679,7 +691,7 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
679
691
  const count = taskCtx.autoResolvedCount;
680
692
  if (count <= 2 || count % 5 === 0) {
681
693
  const excerpt = promptText.length > 120 ? `${promptText.slice(0, 120)}...` : promptText;
682
- ctx.sendChatMessage(`[${taskCtx.label}] Approved: ${excerpt}`, "coding-agent");
694
+ ctx.log(`[${taskCtx.label}] Approved: ${excerpt}`);
683
695
  }
684
696
  return;
685
697
  }
@@ -745,48 +757,16 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
745
757
  const raw = await fetchRecentOutput(ctx, sessionId);
746
758
  turnOutput = cleanForChat(raw);
747
759
  }
748
- const agentDecisionCb = ctx.getAgentDecisionCallback();
749
760
  let decision = null;
750
- let decisionFromPipeline = false;
751
- const triageCtx = {
752
- eventType: "turn_complete",
753
- promptText: "",
754
- recentOutput: turnOutput,
755
- originalTask: taskCtx.originalTask
756
- };
757
- const tier = agentDecisionCb ? await classifyEventTier(ctx.runtime, triageCtx, ctx.log) : "routine";
758
- if (tier === "routine") {
759
- const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
760
- try {
761
- const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
762
- prompt
763
- });
764
- decision = parseCoordinationResponse(result);
765
- } catch (err) {
766
- ctx.log(`Turn-complete LLM call failed: ${err}`);
767
- }
768
- } else {
769
- if (agentDecisionCb) {
770
- const eventMessage = buildTurnCompleteEventMessage(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
771
- try {
772
- decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
773
- if (decision)
774
- decisionFromPipeline = true;
775
- } catch (err) {
776
- ctx.log(`Agent decision callback failed for turn-complete: ${err} — falling back to small LLM`);
777
- }
778
- }
779
- if (!decision) {
780
- const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
781
- try {
782
- const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
783
- prompt
784
- });
785
- decision = parseCoordinationResponse(result);
786
- } catch (err) {
787
- ctx.log(`Turn-complete LLM fallback call failed: ${err}`);
788
- }
789
- }
761
+ const decisionFromPipeline = false;
762
+ const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
763
+ try {
764
+ const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
765
+ prompt
766
+ });
767
+ decision = parseCoordinationResponse(result);
768
+ } catch (err) {
769
+ ctx.log(`Turn-complete LLM call failed: ${err}`);
790
770
  }
791
771
  if (!decision) {
792
772
  ctx.log(`Turn-complete for "${taskCtx.label}": all decision paths failed — escalating`);
@@ -817,7 +797,7 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
817
797
  if (decision.action === "respond") {
818
798
  const instruction = decision.response ?? "";
819
799
  const preview = instruction.length > 120 ? `${instruction.slice(0, 120)}...` : instruction;
820
- ctx.sendChatMessage(`[${taskCtx.label}] Turn done, continuing: ${preview}`, "coding-agent");
800
+ ctx.log(`[${taskCtx.label}] Turn done, continuing: ${preview}`);
821
801
  } else if (decision.action === "escalate") {
822
802
  ctx.sendChatMessage(`[${taskCtx.label}] Turn finished — needs your attention: ${decision.reasoning}`, "coding-agent");
823
803
  }
@@ -856,7 +836,7 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
856
836
  if (agentDecisionCb) {
857
837
  const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx));
858
838
  try {
859
- decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
839
+ decision = await withTimeout(agentDecisionCb(eventMessage, sessionId, taskCtx), DECISION_CB_TIMEOUT_MS, "agentDecisionCb");
860
840
  if (decision)
861
841
  decisionFromPipeline = true;
862
842
  } catch (err) {
@@ -919,7 +899,7 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
919
899
  if (decision.action === "respond") {
920
900
  const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : decision.response ? `Responded: ${decision.response.length > 100 ? `${decision.response.slice(0, 100)}...` : decision.response}` : "Responded";
921
901
  const reasonExcerpt = decision.reasoning.length > 150 ? `${decision.reasoning.slice(0, 150)}...` : decision.reasoning;
922
- ctx.sendChatMessage(`[${taskCtx.label}] ${actionDesc} — ${reasonExcerpt}`, "coding-agent");
902
+ ctx.log(`[${taskCtx.label}] ${actionDesc} — ${reasonExcerpt}`);
923
903
  } else if (decision.action === "escalate") {
924
904
  ctx.sendChatMessage(`[${taskCtx.label}] Needs your attention: ${decision.reasoning}`, "coding-agent");
925
905
  }
@@ -956,7 +936,7 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
956
936
  if (agentDecisionCb) {
957
937
  const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx));
958
938
  try {
959
- decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
939
+ decision = await withTimeout(agentDecisionCb(eventMessage, sessionId, taskCtx), DECISION_CB_TIMEOUT_MS, "agentDecisionCb");
960
940
  if (decision)
961
941
  decisionFromPipeline = true;
962
942
  } catch (err) {
@@ -1006,7 +986,7 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
1006
986
  await drainPendingTurnComplete(ctx, sessionId);
1007
987
  }
1008
988
  }
1009
- var MAX_AUTO_RESPONSES = 10;
989
+ var DECISION_CB_TIMEOUT_MS = 30000, MAX_AUTO_RESPONSES = 10;
1010
990
  var init_swarm_decision_loop = __esm(() => {
1011
991
  init_ansi_utils();
1012
992
  init_swarm_event_triage();
@@ -2033,8 +2013,8 @@ import {
2033
2013
  } from "@elizaos/core";
2034
2014
 
2035
2015
  // src/services/pty-service.ts
2036
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2037
- import { dirname, join } from "node:path";
2016
+ import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
2017
+ import { dirname, join as join2 } from "node:path";
2038
2018
  import { logger as logger2 } from "@elizaos/core";
2039
2019
  import {
2040
2020
  checkAdapters,
@@ -2349,6 +2329,8 @@ async function initializePTYManager(ctx) {
2349
2329
  }
2350
2330
 
2351
2331
  // src/services/pty-session-io.ts
2332
+ import { readFile, writeFile } from "node:fs/promises";
2333
+ import { join } from "node:path";
2352
2334
  async function sendToSession(ctx, sessionId, input) {
2353
2335
  const session = ctx.manager.get(sessionId);
2354
2336
  if (!session) {
@@ -2403,6 +2385,12 @@ async function stopSession(ctx, sessionId, sessionMetadata, sessionWorkdirs, log
2403
2385
  }
2404
2386
  } catch {}
2405
2387
  ctx.outputUnsubscribers.delete(sessionId);
2388
+ const workdir = sessionWorkdirs.get(sessionId);
2389
+ if (workdir) {
2390
+ try {
2391
+ await cleanupClaudeHooks(workdir, log);
2392
+ } catch {}
2393
+ }
2406
2394
  sessionMetadata.delete(sessionId);
2407
2395
  sessionWorkdirs.delete(sessionId);
2408
2396
  ctx.sessionOutputBuffers.delete(sessionId);
@@ -2410,6 +2398,18 @@ async function stopSession(ctx, sessionId, sessionMetadata, sessionWorkdirs, log
2410
2398
  log(`Stopped session ${sessionId}`);
2411
2399
  }
2412
2400
  }
2401
+ async function cleanupClaudeHooks(workdir, log) {
2402
+ const settingsPath = join(workdir, ".claude", "settings.json");
2403
+ try {
2404
+ const raw = await readFile(settingsPath, "utf-8");
2405
+ const settings = JSON.parse(raw);
2406
+ if (!settings.hooks)
2407
+ return;
2408
+ delete settings.hooks;
2409
+ await writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
2410
+ log(`Cleaned up hooks from ${settingsPath}`);
2411
+ } catch {}
2412
+ }
2413
2413
  function subscribeToOutput(ctx, sessionId, callback) {
2414
2414
  if (ctx.usingBunWorker) {
2415
2415
  const unsubscribe2 = ctx.manager.onSessionData(sessionId, callback);
@@ -2521,7 +2521,7 @@ function setupDeferredTaskDelivery(ctx, session, task, agentType) {
2521
2521
  taskSent = true;
2522
2522
  if (readyTimeout)
2523
2523
  clearTimeout(readyTimeout);
2524
- ctx.markTaskDelivered?.(sid);
2524
+ ctx.markTaskDelivered(sid);
2525
2525
  setTimeout(() => sendTaskWithRetry(0), settleMs);
2526
2526
  if (ctx.usingBunWorker) {
2527
2527
  ctx.manager.removeListener("session_ready", onReady);
@@ -2570,7 +2570,12 @@ function buildSpawnConfig(sessionId, options, workdir) {
2570
2570
  type: options.agentType,
2571
2571
  workdir,
2572
2572
  inheritProcessEnv: false,
2573
- env: { ...buildSanitizedBaseEnv(), ...options.env, ...modelEnv },
2573
+ env: {
2574
+ ...buildSanitizedBaseEnv(),
2575
+ ...options.env,
2576
+ ...modelEnv,
2577
+ PARALLAX_SESSION_ID: sessionId
2578
+ },
2574
2579
  ...options.skipAdapterAutoResponse ? { skipAdapterAutoResponse: true } : {},
2575
2580
  adapterConfig: {
2576
2581
  ...options.credentials,
@@ -3062,7 +3067,7 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
3062
3067
  });
3063
3068
  if (decision.action === "complete") {} else if (decision.action === "respond") {
3064
3069
  const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : `Nudged: ${decision.response ?? ""}`;
3065
- ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m — ${actionDesc}`, "coding-agent");
3070
+ ctx.log(`[${taskCtx.label}] Idle for ${idleMinutes}m — ${actionDesc}`);
3066
3071
  } else if (decision.action === "escalate") {
3067
3072
  ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m — needs your attention: ${decision.reasoning}`, "coding-agent");
3068
3073
  } else if (decision.action === "ignore") {
@@ -3401,6 +3406,9 @@ class SwarmCoordinator {
3401
3406
  data
3402
3407
  });
3403
3408
  const toolData = data;
3409
+ if (toolData.source === "hook") {
3410
+ break;
3411
+ }
3404
3412
  const now = Date.now();
3405
3413
  const STARTUP_GRACE_MS = 1e4;
3406
3414
  if (now - taskCtx.registeredAt < STARTUP_GRACE_MS) {
@@ -3420,7 +3428,7 @@ class SwarmCoordinator {
3420
3428
  }
3421
3429
  } catch {}
3422
3430
  }
3423
- this.sendChatMessage(`[${taskCtx.label}] Running ${toolDesc}.${urlSuffix} The agent is working outside the terminal — I'll let it finish.`, "coding-agent");
3431
+ this.log(`[${taskCtx.label}] Running ${toolDesc}.${urlSuffix} The agent is working outside the terminal.`);
3424
3432
  }
3425
3433
  break;
3426
3434
  }
@@ -3705,19 +3713,29 @@ class PTYService {
3705
3713
  }
3706
3714
  if (resolvedAgentType === "claude") {
3707
3715
  try {
3708
- const settingsPath = join(workdir, ".claude", "settings.json");
3716
+ const settingsPath = join2(workdir, ".claude", "settings.json");
3709
3717
  let settings = {};
3710
3718
  try {
3711
- settings = JSON.parse(await readFile(settingsPath, "utf-8"));
3719
+ settings = JSON.parse(await readFile2(settingsPath, "utf-8"));
3712
3720
  } catch {}
3713
3721
  const permissions = settings.permissions ?? {};
3714
3722
  permissions.allowedDirectories = [workdir];
3715
3723
  settings.permissions = permissions;
3724
+ const serverPort = this.runtime.getSetting("SERVER_PORT") ?? "2138";
3725
+ const adapter = this.getAdapter("claude");
3726
+ const hookProtocol = adapter.getHookTelemetryProtocol({
3727
+ httpUrl: `http://localhost:${serverPort}/api/coding-agents/hooks`,
3728
+ sessionId
3729
+ });
3730
+ if (hookProtocol) {
3731
+ settings.hooks = hookProtocol.settingsHooks;
3732
+ this.log(`Injecting HTTP hooks for session ${sessionId}`);
3733
+ }
3716
3734
  await mkdir(dirname(settingsPath), { recursive: true });
3717
- await writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3735
+ await writeFile2(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3718
3736
  this.log(`Wrote allowedDirectories [${workdir}] to ${settingsPath}`);
3719
3737
  } catch (err) {
3720
- this.log(`Failed to write allowedDirectories: ${err}`);
3738
+ this.log(`Failed to write Claude settings: ${err}`);
3721
3739
  }
3722
3740
  }
3723
3741
  const spawnConfig = buildSpawnConfig(sessionId, {
@@ -3861,6 +3879,36 @@ class PTYService {
3861
3879
  const session = this.getSession(sessionId);
3862
3880
  return session?.status === "authenticating";
3863
3881
  }
3882
+ findSessionIdByCwd(cwd) {
3883
+ for (const [sessionId, workdir] of this.sessionWorkdirs) {
3884
+ if (workdir === cwd)
3885
+ return sessionId;
3886
+ }
3887
+ return;
3888
+ }
3889
+ handleHookEvent(sessionId, event, data) {
3890
+ const summary = event === "tool_running" ? `tool=${data.toolName ?? "?"}` : event === "permission_approved" ? `tool=${data.tool ?? "?"}` : JSON.stringify(data);
3891
+ if (event === "tool_running" || event === "permission_approved") {
3892
+ logger2.debug(`[PTYService] Hook event for ${sessionId}: ${event} ${summary}`);
3893
+ } else {
3894
+ this.log(`Hook event for ${sessionId}: ${event} ${summary}`);
3895
+ }
3896
+ switch (event) {
3897
+ case "tool_running":
3898
+ this.emitEvent(sessionId, "tool_running", data);
3899
+ break;
3900
+ case "task_complete":
3901
+ this.emitEvent(sessionId, "task_complete", data);
3902
+ break;
3903
+ case "permission_approved":
3904
+ break;
3905
+ case "notification":
3906
+ this.emitEvent(sessionId, "message", data);
3907
+ break;
3908
+ default:
3909
+ break;
3910
+ }
3911
+ }
3864
3912
  async checkAvailableAgents(types) {
3865
3913
  const agentTypes = types ?? ["claude", "gemini", "codex", "aider"];
3866
3914
  return checkAdapters(agentTypes);
@@ -3988,9 +4036,7 @@ class PTYService {
3988
4036
  return this.metricsTracker.getAll();
3989
4037
  }
3990
4038
  log(message) {
3991
- if (this.serviceConfig.debug) {
3992
- logger2.debug(`[PTYService] ${message}`);
3993
- }
4039
+ logger2.debug(`[PTYService] ${message}`);
3994
4040
  }
3995
4041
  }
3996
4042
 
@@ -6247,6 +6293,93 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
6247
6293
  return false;
6248
6294
  }
6249
6295
 
6296
+ // src/api/hook-routes.ts
6297
+ async function handleHookRoutes(req, res, pathname, ctx) {
6298
+ if (pathname !== "/api/coding-agents/hooks")
6299
+ return false;
6300
+ const method = req.method?.toUpperCase();
6301
+ if (method !== "POST") {
6302
+ sendError(res, "Method not allowed", 405);
6303
+ return true;
6304
+ }
6305
+ if (!ctx.ptyService) {
6306
+ sendError(res, "PTY Service not available", 503);
6307
+ return true;
6308
+ }
6309
+ let body;
6310
+ try {
6311
+ body = await parseBody(req);
6312
+ } catch (err) {
6313
+ sendError(res, err instanceof Error ? err.message : "Failed to parse request body", 400);
6314
+ return true;
6315
+ }
6316
+ const payload = body;
6317
+ const eventName = payload.hook_event_name;
6318
+ if (!eventName) {
6319
+ sendError(res, "Missing hook_event_name", 400);
6320
+ return true;
6321
+ }
6322
+ const headerSessionId = req.headers["x-parallax-session-id"];
6323
+ const sessionId = headerSessionId ? headerSessionId : payload.cwd ? ctx.ptyService.findSessionIdByCwd(payload.cwd) : undefined;
6324
+ if (!sessionId) {
6325
+ sendJson(res, { status: "ignored", reason: "session_not_found" });
6326
+ return true;
6327
+ }
6328
+ switch (eventName) {
6329
+ case "PermissionRequest": {
6330
+ sendJson(res, {
6331
+ hookSpecificOutput: {
6332
+ hookEventName: "PermissionRequest",
6333
+ decision: { behavior: "allow" }
6334
+ }
6335
+ });
6336
+ ctx.ptyService.handleHookEvent(sessionId, "permission_approved", {
6337
+ tool: payload.tool_name
6338
+ });
6339
+ return true;
6340
+ }
6341
+ case "PreToolUse": {
6342
+ ctx.ptyService.handleHookEvent(sessionId, "tool_running", {
6343
+ toolName: payload.tool_name,
6344
+ source: "hook"
6345
+ });
6346
+ sendJson(res, {
6347
+ hookSpecificOutput: {
6348
+ hookEventName: "PreToolUse",
6349
+ permissionDecision: "allow"
6350
+ }
6351
+ });
6352
+ return true;
6353
+ }
6354
+ case "Stop": {
6355
+ ctx.ptyService.handleHookEvent(sessionId, "task_complete", {
6356
+ source: "hook"
6357
+ });
6358
+ sendJson(res, {});
6359
+ return true;
6360
+ }
6361
+ case "Notification": {
6362
+ ctx.ptyService.handleHookEvent(sessionId, "notification", {
6363
+ type: payload.notification_type,
6364
+ message: payload.message
6365
+ });
6366
+ sendJson(res, {});
6367
+ return true;
6368
+ }
6369
+ case "TaskCompleted": {
6370
+ ctx.ptyService.handleHookEvent(sessionId, "task_complete", {
6371
+ source: "hook_task_completed"
6372
+ });
6373
+ sendJson(res, {});
6374
+ return true;
6375
+ }
6376
+ default: {
6377
+ sendJson(res, { status: "ignored", reason: "unknown_event" });
6378
+ return true;
6379
+ }
6380
+ }
6381
+ }
6382
+
6250
6383
  // src/api/issue-routes.ts
6251
6384
  async function handleIssueRoutes(req, res, pathname, ctx) {
6252
6385
  const method = req.method?.toUpperCase();
@@ -6509,6 +6642,9 @@ function sendError(res, message, status = 400) {
6509
6642
  sendJson(res, { error: message }, status);
6510
6643
  }
6511
6644
  async function handleCodingAgentRoutes(req, res, pathname, ctx) {
6645
+ if (await handleHookRoutes(req, res, pathname, ctx)) {
6646
+ return true;
6647
+ }
6512
6648
  if (await handleCoordinatorRoutes(req, res, pathname, ctx)) {
6513
6649
  return true;
6514
6650
  }
@@ -6578,5 +6714,5 @@ export {
6578
6714
  CodingWorkspaceService
6579
6715
  };
6580
6716
 
6581
- //# debugId=87DC30F17636D1DA64756E2164756E21
6717
+ //# debugId=54C728A058C6B9C264756E2164756E21
6582
6718
  //# sourceMappingURL=index.js.map