@elizaos/plugin-agent-orchestrator 0.3.7 → 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,
@@ -2234,6 +2214,7 @@ async function initializePTYManager(ctx) {
2234
2214
  ctx.log(`session_ready event received for ${session.id} (type: ${session.type}, status: ${session.status})`);
2235
2215
  ctx.emitEvent(session.id, "ready", { session });
2236
2216
  forwardReadyAsTaskComplete(ctx, session);
2217
+ ctx.markTaskDelivered?.(session.id);
2237
2218
  });
2238
2219
  bunManager.on("session_exit", (id, code) => {
2239
2220
  ctx.emitEvent(id, "stopped", { reason: `exit code ${code}` });
@@ -2313,6 +2294,7 @@ async function initializePTYManager(ctx) {
2313
2294
  nodeManager.on("session_ready", (session) => {
2314
2295
  ctx.emitEvent(session.id, "ready", { session });
2315
2296
  forwardReadyAsTaskComplete(ctx, session);
2297
+ ctx.markTaskDelivered?.(session.id);
2316
2298
  });
2317
2299
  nodeManager.on("blocking_prompt", (session, promptInfo, autoResponded) => {
2318
2300
  ctx.emitEvent(session.id, "blocked", { promptInfo, autoResponded });
@@ -2347,6 +2329,8 @@ async function initializePTYManager(ctx) {
2347
2329
  }
2348
2330
 
2349
2331
  // src/services/pty-session-io.ts
2332
+ import { readFile, writeFile } from "node:fs/promises";
2333
+ import { join } from "node:path";
2350
2334
  async function sendToSession(ctx, sessionId, input) {
2351
2335
  const session = ctx.manager.get(sessionId);
2352
2336
  if (!session) {
@@ -2401,6 +2385,12 @@ async function stopSession(ctx, sessionId, sessionMetadata, sessionWorkdirs, log
2401
2385
  }
2402
2386
  } catch {}
2403
2387
  ctx.outputUnsubscribers.delete(sessionId);
2388
+ const workdir = sessionWorkdirs.get(sessionId);
2389
+ if (workdir) {
2390
+ try {
2391
+ await cleanupClaudeHooks(workdir, log);
2392
+ } catch {}
2393
+ }
2404
2394
  sessionMetadata.delete(sessionId);
2405
2395
  sessionWorkdirs.delete(sessionId);
2406
2396
  ctx.sessionOutputBuffers.delete(sessionId);
@@ -2408,6 +2398,18 @@ async function stopSession(ctx, sessionId, sessionMetadata, sessionWorkdirs, log
2408
2398
  log(`Stopped session ${sessionId}`);
2409
2399
  }
2410
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
+ }
2411
2413
  function subscribeToOutput(ctx, sessionId, callback) {
2412
2414
  if (ctx.usingBunWorker) {
2413
2415
  const unsubscribe2 = ctx.manager.onSessionData(sessionId, callback);
@@ -2519,6 +2521,7 @@ function setupDeferredTaskDelivery(ctx, session, task, agentType) {
2519
2521
  taskSent = true;
2520
2522
  if (readyTimeout)
2521
2523
  clearTimeout(readyTimeout);
2524
+ ctx.markTaskDelivered(sid);
2522
2525
  setTimeout(() => sendTaskWithRetry(0), settleMs);
2523
2526
  if (ctx.usingBunWorker) {
2524
2527
  ctx.manager.removeListener("session_ready", onReady);
@@ -2567,7 +2570,12 @@ function buildSpawnConfig(sessionId, options, workdir) {
2567
2570
  type: options.agentType,
2568
2571
  workdir,
2569
2572
  inheritProcessEnv: false,
2570
- env: { ...buildSanitizedBaseEnv(), ...options.env, ...modelEnv },
2573
+ env: {
2574
+ ...buildSanitizedBaseEnv(),
2575
+ ...options.env,
2576
+ ...modelEnv,
2577
+ PARALLAX_SESSION_ID: sessionId
2578
+ },
2571
2579
  ...options.skipAdapterAutoResponse ? { skipAdapterAutoResponse: true } : {},
2572
2580
  adapterConfig: {
2573
2581
  ...options.credentials,
@@ -3059,7 +3067,7 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
3059
3067
  });
3060
3068
  if (decision.action === "complete") {} else if (decision.action === "respond") {
3061
3069
  const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : `Nudged: ${decision.response ?? ""}`;
3062
- ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m — ${actionDesc}`, "coding-agent");
3070
+ ctx.log(`[${taskCtx.label}] Idle for ${idleMinutes}m — ${actionDesc}`);
3063
3071
  } else if (decision.action === "escalate") {
3064
3072
  ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m — needs your attention: ${decision.reasoning}`, "coding-agent");
3065
3073
  } else if (decision.action === "ignore") {
@@ -3214,7 +3222,8 @@ class SwarmCoordinator {
3214
3222
  autoResolvedCount: 0,
3215
3223
  registeredAt: Date.now(),
3216
3224
  lastActivityAt: Date.now(),
3217
- idleCheckCount: 0
3225
+ idleCheckCount: 0,
3226
+ taskDelivered: false
3218
3227
  });
3219
3228
  this.broadcast({
3220
3229
  type: "task_registered",
@@ -3397,6 +3406,9 @@ class SwarmCoordinator {
3397
3406
  data
3398
3407
  });
3399
3408
  const toolData = data;
3409
+ if (toolData.source === "hook") {
3410
+ break;
3411
+ }
3400
3412
  const now = Date.now();
3401
3413
  const STARTUP_GRACE_MS = 1e4;
3402
3414
  if (now - taskCtx.registeredAt < STARTUP_GRACE_MS) {
@@ -3416,7 +3428,7 @@ class SwarmCoordinator {
3416
3428
  }
3417
3429
  } catch {}
3418
3430
  }
3419
- 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.`);
3420
3432
  }
3421
3433
  break;
3422
3434
  }
@@ -3603,7 +3615,17 @@ class PTYService {
3603
3615
  if (!coordinator)
3604
3616
  return false;
3605
3617
  const taskCtx = coordinator.getTaskContext(sessionId);
3606
- return (taskCtx?.decisions.length ?? 0) > 0;
3618
+ if (!taskCtx)
3619
+ return false;
3620
+ return taskCtx.taskDelivered || taskCtx.decisions.length > 0;
3621
+ },
3622
+ markTaskDelivered: (sessionId) => {
3623
+ const coordinator = this.coordinator;
3624
+ if (!coordinator)
3625
+ return;
3626
+ const taskCtx = coordinator.getTaskContext(sessionId);
3627
+ if (taskCtx)
3628
+ taskCtx.taskDelivered = true;
3607
3629
  }
3608
3630
  });
3609
3631
  this.manager = result.manager;
@@ -3691,19 +3713,29 @@ class PTYService {
3691
3713
  }
3692
3714
  if (resolvedAgentType === "claude") {
3693
3715
  try {
3694
- const settingsPath = join(workdir, ".claude", "settings.json");
3716
+ const settingsPath = join2(workdir, ".claude", "settings.json");
3695
3717
  let settings = {};
3696
3718
  try {
3697
- settings = JSON.parse(await readFile(settingsPath, "utf-8"));
3719
+ settings = JSON.parse(await readFile2(settingsPath, "utf-8"));
3698
3720
  } catch {}
3699
3721
  const permissions = settings.permissions ?? {};
3700
3722
  permissions.allowedDirectories = [workdir];
3701
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
+ }
3702
3734
  await mkdir(dirname(settingsPath), { recursive: true });
3703
- await writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3735
+ await writeFile2(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3704
3736
  this.log(`Wrote allowedDirectories [${workdir}] to ${settingsPath}`);
3705
3737
  } catch (err) {
3706
- this.log(`Failed to write allowedDirectories: ${err}`);
3738
+ this.log(`Failed to write Claude settings: ${err}`);
3707
3739
  }
3708
3740
  }
3709
3741
  const spawnConfig = buildSpawnConfig(sessionId, {
@@ -3732,7 +3764,15 @@ class PTYService {
3732
3764
  sendKeysToSession: (id, keys) => this.sendKeysToSession(id, keys),
3733
3765
  pushDefaultRules: (id, type) => this.pushDefaultRules(id, type),
3734
3766
  toSessionInfo: (s, w) => this.toSessionInfo(s, w),
3735
- log: (msg) => this.log(msg)
3767
+ log: (msg) => this.log(msg),
3768
+ markTaskDelivered: (sessionId2) => {
3769
+ const coordinator = this.coordinator;
3770
+ if (!coordinator)
3771
+ return;
3772
+ const taskCtx = coordinator.getTaskContext(sessionId2);
3773
+ if (taskCtx)
3774
+ taskCtx.taskDelivered = true;
3775
+ }
3736
3776
  };
3737
3777
  if (this.usingBunWorker) {
3738
3778
  setupOutputBuffer(ctx, session.id);
@@ -3839,6 +3879,36 @@ class PTYService {
3839
3879
  const session = this.getSession(sessionId);
3840
3880
  return session?.status === "authenticating";
3841
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
+ }
3842
3912
  async checkAvailableAgents(types) {
3843
3913
  const agentTypes = types ?? ["claude", "gemini", "codex", "aider"];
3844
3914
  return checkAdapters(agentTypes);
@@ -3966,9 +4036,7 @@ class PTYService {
3966
4036
  return this.metricsTracker.getAll();
3967
4037
  }
3968
4038
  log(message) {
3969
- if (this.serviceConfig.debug) {
3970
- logger2.debug(`[PTYService] ${message}`);
3971
- }
4039
+ logger2.debug(`[PTYService] ${message}`);
3972
4040
  }
3973
4041
  }
3974
4042
 
@@ -6225,6 +6293,93 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
6225
6293
  return false;
6226
6294
  }
6227
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
+
6228
6383
  // src/api/issue-routes.ts
6229
6384
  async function handleIssueRoutes(req, res, pathname, ctx) {
6230
6385
  const method = req.method?.toUpperCase();
@@ -6487,6 +6642,9 @@ function sendError(res, message, status = 400) {
6487
6642
  sendJson(res, { error: message }, status);
6488
6643
  }
6489
6644
  async function handleCodingAgentRoutes(req, res, pathname, ctx) {
6645
+ if (await handleHookRoutes(req, res, pathname, ctx)) {
6646
+ return true;
6647
+ }
6490
6648
  if (await handleCoordinatorRoutes(req, res, pathname, ctx)) {
6491
6649
  return true;
6492
6650
  }
@@ -6556,5 +6714,5 @@ export {
6556
6714
  CodingWorkspaceService
6557
6715
  };
6558
6716
 
6559
- //# debugId=D5A4E2FBAF42C88664756E2164756E21
6717
+ //# debugId=54C728A058C6B9C264756E2164756E21
6560
6718
  //# sourceMappingURL=index.js.map