@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.
- 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 +221 -63
- package/dist/index.js.map +12 -11
- package/dist/services/pty-init.d.ts +3 -1
- package/dist/services/pty-init.d.ts.map +1 -1
- 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 +2 -0
- package/dist/services/pty-spawn.d.ts.map +1 -1
- package/dist/services/swarm-coordinator.d.ts +2 -0
- 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,
|
|
@@ -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: {
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
3716
|
+
const settingsPath = join2(workdir, ".claude", "settings.json");
|
|
3695
3717
|
let settings = {};
|
|
3696
3718
|
try {
|
|
3697
|
-
settings = JSON.parse(await
|
|
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
|
|
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
|
|
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
|
-
|
|
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=
|
|
6717
|
+
//# debugId=54C728A058C6B9C264756E2164756E21
|
|
6560
6718
|
//# sourceMappingURL=index.js.map
|