@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.
- package/dist/api/hook-routes.d.ts +17 -0
- package/dist/api/hook-routes.d.ts.map +1 -0
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/index.js +197 -61
- package/dist/index.js.map +11 -10
- package/dist/services/pty-service.d.ts +10 -0
- package/dist/services/pty-service.d.ts.map +1 -1
- package/dist/services/pty-session-io.d.ts.map +1 -1
- package/dist/services/pty-spawn.d.ts +1 -1
- package/dist/services/pty-spawn.d.ts.map +1 -1
- package/dist/services/swarm-coordinator.d.ts.map +1 -1
- package/dist/services/swarm-decision-loop.d.ts.map +1 -1
- package/dist/services/swarm-idle-watchdog.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -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"}
|
package/dist/api/routes.d.ts.map
CHANGED
|
@@ -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;
|
|
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.
|
|
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
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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: {
|
|
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.
|
|
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.
|
|
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 =
|
|
3716
|
+
const settingsPath = join2(workdir, ".claude", "settings.json");
|
|
3709
3717
|
let settings = {};
|
|
3710
3718
|
try {
|
|
3711
|
-
settings = JSON.parse(await
|
|
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
|
|
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
|
|
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
|
-
|
|
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=
|
|
6717
|
+
//# debugId=54C728A058C6B9C264756E2164756E21
|
|
6582
6718
|
//# sourceMappingURL=index.js.map
|