@elizaos/plugin-agent-orchestrator 0.3.11 → 0.3.13
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/agent-routes.d.ts.map +1 -1
- package/dist/index.js +382 -53
- package/dist/index.js.map +10 -9
- package/dist/services/debug-capture.d.ts +38 -0
- package/dist/services/debug-capture.d.ts.map +1 -0
- package/dist/services/pty-service.d.ts.map +1 -1
- package/dist/services/swarm-coordinator.d.ts +5 -0
- package/dist/services/swarm-coordinator.d.ts.map +1 -1
- package/dist/services/swarm-decision-loop.d.ts +8 -0
- package/dist/services/swarm-decision-loop.d.ts.map +1 -1
- package/dist/services/swarm-idle-watchdog.d.ts.map +1 -1
- package/dist/services/trajectory-feedback.d.ts +1 -1
- package/dist/services/trajectory-feedback.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -537,7 +537,9 @@ __export(exports_swarm_decision_loop, {
|
|
|
537
537
|
handleBlocked: () => handleBlocked,
|
|
538
538
|
handleAutonomousDecision: () => handleAutonomousDecision,
|
|
539
539
|
executeDecision: () => executeDecision,
|
|
540
|
-
|
|
540
|
+
clearDeferredTurnCompleteTimers: () => clearDeferredTurnCompleteTimers,
|
|
541
|
+
checkAllTasksComplete: () => checkAllTasksComplete,
|
|
542
|
+
POST_SEND_COOLDOWN_MS: () => POST_SEND_COOLDOWN_MS
|
|
541
543
|
});
|
|
542
544
|
import * as path from "node:path";
|
|
543
545
|
import { ModelType as ModelType3 } from "@elizaos/core";
|
|
@@ -553,6 +555,12 @@ function withTimeout(promise, ms, label) {
|
|
|
553
555
|
});
|
|
554
556
|
});
|
|
555
557
|
}
|
|
558
|
+
function clearDeferredTurnCompleteTimers() {
|
|
559
|
+
for (const timer of deferredTurnCompleteTimers.values()) {
|
|
560
|
+
clearTimeout(timer);
|
|
561
|
+
}
|
|
562
|
+
deferredTurnCompleteTimers.clear();
|
|
563
|
+
}
|
|
556
564
|
function toContextSummary(taskCtx) {
|
|
557
565
|
return {
|
|
558
566
|
sessionId: taskCtx.sessionId,
|
|
@@ -732,14 +740,23 @@ function checkAllTasksComplete(ctx) {
|
|
|
732
740
|
};
|
|
733
741
|
if (swarmCompleteCb) {
|
|
734
742
|
ctx.log("checkAllTasksComplete: swarm complete callback is wired — calling synthesis");
|
|
735
|
-
const taskSummaries = tasks.map((t) =>
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
+
const taskSummaries = tasks.map((t) => {
|
|
744
|
+
const decisions = ctx.sharedDecisions.filter((sd) => sd.agentLabel === t.label).map((sd) => sd.summary);
|
|
745
|
+
const summaryParts = [];
|
|
746
|
+
if (decisions.length > 0)
|
|
747
|
+
summaryParts.push(decisions.join("; "));
|
|
748
|
+
if (t.completionSummary)
|
|
749
|
+
summaryParts.push(t.completionSummary);
|
|
750
|
+
return {
|
|
751
|
+
sessionId: t.sessionId,
|
|
752
|
+
label: t.label,
|
|
753
|
+
agentType: t.agentType,
|
|
754
|
+
originalTask: t.originalTask,
|
|
755
|
+
status: t.status,
|
|
756
|
+
completionSummary: summaryParts.join(`
|
|
757
|
+
`) || ""
|
|
758
|
+
};
|
|
759
|
+
});
|
|
743
760
|
withTimeout(Promise.resolve().then(() => swarmCompleteCb({
|
|
744
761
|
tasks: taskSummaries,
|
|
745
762
|
total: tasks.length,
|
|
@@ -786,7 +803,8 @@ async function executeDecision(ctx, sessionId, decision) {
|
|
|
786
803
|
if (!ctx.ptyService)
|
|
787
804
|
return;
|
|
788
805
|
switch (decision.action) {
|
|
789
|
-
case "respond":
|
|
806
|
+
case "respond": {
|
|
807
|
+
const taskCtx = ctx.tasks.get(sessionId);
|
|
790
808
|
if (decision.useKeys && decision.keys) {
|
|
791
809
|
await ctx.ptyService.sendKeysToSession(sessionId, decision.keys);
|
|
792
810
|
} else if (decision.response !== undefined) {
|
|
@@ -796,7 +814,10 @@ async function executeDecision(ctx, sessionId, decision) {
|
|
|
796
814
|
commitSharedDecisionIndex(ctx, sessionId, snapshotIndex);
|
|
797
815
|
}
|
|
798
816
|
}
|
|
817
|
+
if (taskCtx)
|
|
818
|
+
taskCtx.lastInputSentAt = Date.now();
|
|
799
819
|
break;
|
|
820
|
+
}
|
|
800
821
|
case "complete": {
|
|
801
822
|
const taskCtx = ctx.tasks.get(sessionId);
|
|
802
823
|
if (taskCtx) {
|
|
@@ -953,11 +974,45 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
953
974
|
}
|
|
954
975
|
}
|
|
955
976
|
async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
977
|
+
if (taskCtx.status !== "active")
|
|
978
|
+
return;
|
|
956
979
|
if (ctx.inFlightDecisions.has(sessionId)) {
|
|
957
980
|
ctx.log(`Buffering turn-complete for ${sessionId} (in-flight decision running)`);
|
|
958
981
|
ctx.pendingTurnComplete.set(sessionId, data);
|
|
959
982
|
return;
|
|
960
983
|
}
|
|
984
|
+
if (taskCtx.lastInputSentAt) {
|
|
985
|
+
const elapsed = Date.now() - taskCtx.lastInputSentAt;
|
|
986
|
+
if (elapsed < POST_SEND_COOLDOWN_MS) {
|
|
987
|
+
ctx.pendingTurnComplete.set(sessionId, data);
|
|
988
|
+
if (!deferredTurnCompleteTimers.has(sessionId)) {
|
|
989
|
+
const delayMs = POST_SEND_COOLDOWN_MS - elapsed + 50;
|
|
990
|
+
const timer = setTimeout(() => {
|
|
991
|
+
deferredTurnCompleteTimers.delete(sessionId);
|
|
992
|
+
const pendingData = ctx.pendingTurnComplete.get(sessionId);
|
|
993
|
+
if (!pendingData)
|
|
994
|
+
return;
|
|
995
|
+
const currentTask = ctx.tasks.get(sessionId);
|
|
996
|
+
if (!currentTask || currentTask.status !== "active") {
|
|
997
|
+
ctx.pendingTurnComplete.delete(sessionId);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
handleTurnComplete(ctx, sessionId, currentTask, pendingData).catch((err) => {
|
|
1001
|
+
ctx.log(`Deferred turn-complete replay failed for ${sessionId}: ${err}`);
|
|
1002
|
+
});
|
|
1003
|
+
}, delayMs);
|
|
1004
|
+
deferredTurnCompleteTimers.set(sessionId, timer);
|
|
1005
|
+
}
|
|
1006
|
+
ctx.log(`Suppressing turn-complete for "${taskCtx.label}" — ` + `${Math.round(elapsed / 1000)}s since last input (cooldown ${POST_SEND_COOLDOWN_MS / 1000}s)`);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const deferredTimer = deferredTurnCompleteTimers.get(sessionId);
|
|
1011
|
+
if (deferredTimer) {
|
|
1012
|
+
clearTimeout(deferredTimer);
|
|
1013
|
+
deferredTurnCompleteTimers.delete(sessionId);
|
|
1014
|
+
}
|
|
1015
|
+
ctx.pendingTurnComplete.delete(sessionId);
|
|
961
1016
|
ctx.inFlightDecisions.add(sessionId);
|
|
962
1017
|
try {
|
|
963
1018
|
ctx.log(`Turn complete for "${taskCtx.label}" — assessing whether task is done`);
|
|
@@ -1207,10 +1262,11 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
1207
1262
|
await drainPendingBlocked(ctx, sessionId);
|
|
1208
1263
|
}
|
|
1209
1264
|
}
|
|
1210
|
-
var DECISION_CB_TIMEOUT_MS = 30000, MAX_AUTO_RESPONSES = 10;
|
|
1265
|
+
var DECISION_CB_TIMEOUT_MS = 30000, MAX_AUTO_RESPONSES = 10, POST_SEND_COOLDOWN_MS = 15000, deferredTurnCompleteTimers;
|
|
1211
1266
|
var init_swarm_decision_loop = __esm(() => {
|
|
1212
1267
|
init_ansi_utils();
|
|
1213
1268
|
init_swarm_event_triage();
|
|
1269
|
+
deferredTurnCompleteTimers = new Map;
|
|
1214
1270
|
});
|
|
1215
1271
|
|
|
1216
1272
|
// src/actions/finalize-workspace.ts
|
|
@@ -2230,13 +2286,13 @@ var sendToAgentAction = {
|
|
|
2230
2286
|
import * as os from "node:os";
|
|
2231
2287
|
import * as path2 from "node:path";
|
|
2232
2288
|
import {
|
|
2233
|
-
logger as
|
|
2289
|
+
logger as logger4
|
|
2234
2290
|
} from "@elizaos/core";
|
|
2235
2291
|
|
|
2236
2292
|
// src/services/pty-service.ts
|
|
2237
2293
|
import { appendFile, mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2238
2294
|
import { dirname, join as join2 } from "node:path";
|
|
2239
|
-
import { logger as
|
|
2295
|
+
import { logger as logger3 } from "@elizaos/core";
|
|
2240
2296
|
import {
|
|
2241
2297
|
checkAdapters,
|
|
2242
2298
|
createAdapter,
|
|
@@ -3141,6 +3197,9 @@ async function classifyAndDecideForCoordinator(ctx) {
|
|
|
3141
3197
|
}
|
|
3142
3198
|
}
|
|
3143
3199
|
|
|
3200
|
+
// src/services/pty-service.ts
|
|
3201
|
+
init_swarm_decision_loop();
|
|
3202
|
+
|
|
3144
3203
|
// src/services/swarm-coordinator.ts
|
|
3145
3204
|
init_ansi_utils();
|
|
3146
3205
|
init_swarm_decision_loop();
|
|
@@ -3162,6 +3221,7 @@ async function scanIdleSessions(ctx) {
|
|
|
3162
3221
|
if (!session) {
|
|
3163
3222
|
ctx.log(`Idle watchdog: "${taskCtx.label}" — PTY session no longer exists, marking as stopped`);
|
|
3164
3223
|
taskCtx.status = "stopped";
|
|
3224
|
+
taskCtx.stoppedAt = now;
|
|
3165
3225
|
taskCtx.decisions.push({
|
|
3166
3226
|
timestamp: now,
|
|
3167
3227
|
event: "idle_watchdog",
|
|
@@ -3205,6 +3265,7 @@ async function scanIdleSessions(ctx) {
|
|
|
3205
3265
|
if (taskCtx.idleCheckCount >= MAX_IDLE_CHECKS) {
|
|
3206
3266
|
ctx.log(`Idle watchdog: force-stopping "${taskCtx.label}" after ${MAX_IDLE_CHECKS} checks`);
|
|
3207
3267
|
taskCtx.status = "stopped";
|
|
3268
|
+
taskCtx.stoppedAt = now;
|
|
3208
3269
|
taskCtx.decisions.push({
|
|
3209
3270
|
timestamp: now,
|
|
3210
3271
|
event: "idle_watchdog",
|
|
@@ -3339,6 +3400,7 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
3339
3400
|
var UNREGISTERED_BUFFER_MS = 2000;
|
|
3340
3401
|
var IDLE_SCAN_INTERVAL_MS = 60 * 1000;
|
|
3341
3402
|
var PAUSE_TIMEOUT_MS = 30000;
|
|
3403
|
+
var STOPPED_RECOVERY_WINDOW_MS = 90000;
|
|
3342
3404
|
|
|
3343
3405
|
class SwarmCoordinator {
|
|
3344
3406
|
static serviceType = "SWARM_COORDINATOR";
|
|
@@ -3439,6 +3501,7 @@ class SwarmCoordinator {
|
|
|
3439
3501
|
this.pendingDecisions.clear();
|
|
3440
3502
|
this.inFlightDecisions.clear();
|
|
3441
3503
|
this.pendingTurnComplete.clear();
|
|
3504
|
+
clearDeferredTurnCompleteTimers();
|
|
3442
3505
|
this.lastBlockedPromptFingerprint.clear();
|
|
3443
3506
|
this.pendingBlocked.clear();
|
|
3444
3507
|
this.unregisteredBuffer.clear();
|
|
@@ -3620,8 +3683,21 @@ class SwarmCoordinator {
|
|
|
3620
3683
|
}
|
|
3621
3684
|
return;
|
|
3622
3685
|
}
|
|
3686
|
+
let recoveredFromStopped = false;
|
|
3623
3687
|
if (taskCtx.status === "stopped" || taskCtx.status === "error" || taskCtx.status === "completed") {
|
|
3624
|
-
if (
|
|
3688
|
+
if (taskCtx.status === "stopped" && event === "task_complete") {
|
|
3689
|
+
const stoppedAt = taskCtx.stoppedAt ?? 0;
|
|
3690
|
+
const ageMs = Date.now() - stoppedAt;
|
|
3691
|
+
if (stoppedAt > 0 && ageMs <= STOPPED_RECOVERY_WINDOW_MS) {
|
|
3692
|
+
this.log(`Recovering "${taskCtx.label}" from stopped on late task_complete (${Math.round(ageMs / 1000)}s old)`);
|
|
3693
|
+
taskCtx.status = "active";
|
|
3694
|
+
recoveredFromStopped = true;
|
|
3695
|
+
} else {
|
|
3696
|
+
this.log(`Ignoring "${event}" for ${taskCtx.label} (status: stopped, age=${Math.round(ageMs / 1000)}s)`);
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
if (!recoveredFromStopped && event !== "stopped" && event !== "error") {
|
|
3625
3701
|
this.log(`Ignoring "${event}" for ${taskCtx.label} (status: ${taskCtx.status})`);
|
|
3626
3702
|
return;
|
|
3627
3703
|
}
|
|
@@ -3672,6 +3748,7 @@ class SwarmCoordinator {
|
|
|
3672
3748
|
case "stopped":
|
|
3673
3749
|
if (taskCtx.status !== "completed" && taskCtx.status !== "error") {
|
|
3674
3750
|
taskCtx.status = "stopped";
|
|
3751
|
+
taskCtx.stoppedAt = Date.now();
|
|
3675
3752
|
}
|
|
3676
3753
|
this.inFlightDecisions.delete(sessionId);
|
|
3677
3754
|
this.broadcast({
|
|
@@ -3822,6 +3899,65 @@ class SwarmCoordinator {
|
|
|
3822
3899
|
}
|
|
3823
3900
|
}
|
|
3824
3901
|
|
|
3902
|
+
// src/services/debug-capture.ts
|
|
3903
|
+
import { logger as logger2 } from "@elizaos/core";
|
|
3904
|
+
var captureManager = null;
|
|
3905
|
+
var initAttempted = false;
|
|
3906
|
+
function isDebugCaptureEnabled() {
|
|
3907
|
+
return process.env.PARALLAX_DEBUG_CAPTURE === "1";
|
|
3908
|
+
}
|
|
3909
|
+
async function ensureCaptureManager() {
|
|
3910
|
+
if (captureManager)
|
|
3911
|
+
return captureManager;
|
|
3912
|
+
if (initAttempted)
|
|
3913
|
+
return null;
|
|
3914
|
+
initAttempted = true;
|
|
3915
|
+
if (!isDebugCaptureEnabled())
|
|
3916
|
+
return null;
|
|
3917
|
+
try {
|
|
3918
|
+
const mod = await import("pty-state-capture");
|
|
3919
|
+
const { PTYStateCaptureManager } = mod;
|
|
3920
|
+
captureManager = new PTYStateCaptureManager({
|
|
3921
|
+
outputRootDir: ".parallax/pty-captures",
|
|
3922
|
+
defaultRows: 80,
|
|
3923
|
+
defaultCols: 220
|
|
3924
|
+
});
|
|
3925
|
+
logger2.info("[debug-capture] PTY state capture enabled — writing to .parallax/pty-captures/");
|
|
3926
|
+
return captureManager;
|
|
3927
|
+
} catch {
|
|
3928
|
+
logger2.debug("[debug-capture] pty-state-capture not available — capture disabled");
|
|
3929
|
+
return null;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
async function captureSessionOpen(sessionId, agentType) {
|
|
3933
|
+
const mgr = await ensureCaptureManager();
|
|
3934
|
+
if (!mgr)
|
|
3935
|
+
return;
|
|
3936
|
+
try {
|
|
3937
|
+
await mgr.openSession(sessionId, { source: agentType });
|
|
3938
|
+
} catch (err) {
|
|
3939
|
+
logger2.debug(`[debug-capture] Failed to open session ${sessionId}: ${err}`);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
async function captureFeed(sessionId, chunk, direction = "stdout") {
|
|
3943
|
+
if (!captureManager)
|
|
3944
|
+
return;
|
|
3945
|
+
try {
|
|
3946
|
+
await captureManager.feed(sessionId, chunk, direction);
|
|
3947
|
+
} catch (err) {
|
|
3948
|
+
logger2.debug(`[debug-capture] Feed error for ${sessionId}: ${err}`);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
async function captureLifecycle(sessionId, event, detail) {
|
|
3952
|
+
if (!captureManager)
|
|
3953
|
+
return;
|
|
3954
|
+
try {
|
|
3955
|
+
await captureManager.lifecycle(sessionId, event, detail);
|
|
3956
|
+
} catch (err) {
|
|
3957
|
+
logger2.debug(`[debug-capture] Lifecycle error for ${sessionId}: ${err}`);
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3825
3961
|
// src/services/pty-service.ts
|
|
3826
3962
|
function getCoordinator(runtime) {
|
|
3827
3963
|
const ptyService = runtime.getService("PTY_SERVICE");
|
|
@@ -3865,16 +4001,16 @@ class PTYService {
|
|
|
3865
4001
|
const existing = servicesMap?.get?.("SWARM_COORDINATOR");
|
|
3866
4002
|
if (existing && existing.length > 0) {
|
|
3867
4003
|
service.coordinator = existing[0];
|
|
3868
|
-
|
|
4004
|
+
logger3.info("[PTYService] SwarmCoordinator already registered, skipping duplicate start");
|
|
3869
4005
|
} else {
|
|
3870
4006
|
try {
|
|
3871
4007
|
const coordinator = new SwarmCoordinator(runtime);
|
|
3872
4008
|
coordinator.start(service);
|
|
3873
4009
|
service.coordinator = coordinator;
|
|
3874
4010
|
servicesMap?.set?.("SWARM_COORDINATOR", [coordinator]);
|
|
3875
|
-
|
|
4011
|
+
logger3.info("[PTYService] SwarmCoordinator wired and started");
|
|
3876
4012
|
} catch (err) {
|
|
3877
|
-
|
|
4013
|
+
logger3.error(`[PTYService] Failed to wire SwarmCoordinator: ${err}`);
|
|
3878
4014
|
}
|
|
3879
4015
|
}
|
|
3880
4016
|
return service;
|
|
@@ -4098,6 +4234,21 @@ class PTYService {
|
|
|
4098
4234
|
if (this.usingBunWorker) {
|
|
4099
4235
|
setupOutputBuffer(ctx, session.id);
|
|
4100
4236
|
}
|
|
4237
|
+
if (isDebugCaptureEnabled()) {
|
|
4238
|
+
captureSessionOpen(session.id, resolvedAgentType).catch(() => {});
|
|
4239
|
+
if (this.usingBunWorker) {
|
|
4240
|
+
this.manager.onSessionData(session.id, (data) => {
|
|
4241
|
+
captureFeed(session.id, data, "stdout");
|
|
4242
|
+
});
|
|
4243
|
+
} else {
|
|
4244
|
+
const ptySession = this.manager.getSession(session.id);
|
|
4245
|
+
if (ptySession) {
|
|
4246
|
+
ptySession.on("output", (data) => {
|
|
4247
|
+
captureFeed(session.id, data, "stdout");
|
|
4248
|
+
});
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4101
4252
|
if (resolvedInitialTask) {
|
|
4102
4253
|
setupDeferredTaskDelivery(ctx, session, resolvedInitialTask, resolvedAgentType);
|
|
4103
4254
|
}
|
|
@@ -4125,6 +4276,7 @@ class PTYService {
|
|
|
4125
4276
|
async sendToSession(sessionId, input) {
|
|
4126
4277
|
if (!this.manager)
|
|
4127
4278
|
throw new Error("PTYService not initialized");
|
|
4279
|
+
captureFeed(sessionId, input, "stdin");
|
|
4128
4280
|
return sendToSession(this.ioContext(), sessionId, input);
|
|
4129
4281
|
}
|
|
4130
4282
|
async sendKeysToSession(sessionId, keys) {
|
|
@@ -4135,6 +4287,7 @@ class PTYService {
|
|
|
4135
4287
|
async stopSession(sessionId, force = false) {
|
|
4136
4288
|
if (!this.manager)
|
|
4137
4289
|
throw new Error("PTYService not initialized");
|
|
4290
|
+
captureLifecycle(sessionId, "session_stopped", force ? "force" : undefined);
|
|
4138
4291
|
return stopSession(this.ioContext(), sessionId, this.sessionMetadata, this.sessionWorkdirs, (msg) => this.log(msg), force);
|
|
4139
4292
|
}
|
|
4140
4293
|
get defaultApprovalPreset() {
|
|
@@ -4210,12 +4363,12 @@ class PTYService {
|
|
|
4210
4363
|
handleHookEvent(sessionId, event, data) {
|
|
4211
4364
|
const summary = event === "tool_running" ? `tool=${data.toolName ?? "?"}` : event === "permission_approved" ? `tool=${data.tool ?? "?"}` : JSON.stringify(data);
|
|
4212
4365
|
if (event === "tool_running" || event === "permission_approved") {
|
|
4213
|
-
|
|
4366
|
+
logger3.debug(`[PTYService] Hook event for ${sessionId}: ${event} ${summary}`);
|
|
4214
4367
|
} else {
|
|
4215
4368
|
this.log(`Hook event for ${sessionId}: ${event} ${summary}`);
|
|
4216
4369
|
}
|
|
4217
4370
|
if (this.manager && this.usingBunWorker) {
|
|
4218
|
-
this.manager.notifyHookEvent(sessionId, event).catch((err) =>
|
|
4371
|
+
this.manager.notifyHookEvent(sessionId, event).catch((err) => logger3.debug(`[PTYService] Failed to forward hook event to session: ${err}`));
|
|
4219
4372
|
}
|
|
4220
4373
|
switch (event) {
|
|
4221
4374
|
case "tool_running":
|
|
@@ -4249,6 +4402,13 @@ class PTYService {
|
|
|
4249
4402
|
if (meta?.coordinatorManaged && this.coordinator?.getSupervisionLevel() === "autonomous") {
|
|
4250
4403
|
const taskCtx = this.coordinator.getTaskContext(sessionId);
|
|
4251
4404
|
if (taskCtx) {
|
|
4405
|
+
if (taskCtx.lastInputSentAt) {
|
|
4406
|
+
const elapsed = Date.now() - taskCtx.lastInputSentAt;
|
|
4407
|
+
if (elapsed < POST_SEND_COOLDOWN_MS) {
|
|
4408
|
+
this.log(`Suppressing stall classification for ${sessionId} — ` + `${Math.round(elapsed / 1000)}s since coordinator sent input`);
|
|
4409
|
+
return null;
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4252
4412
|
return classifyAndDecideForCoordinator({
|
|
4253
4413
|
sessionId,
|
|
4254
4414
|
recentOutput,
|
|
@@ -4413,7 +4573,7 @@ class PTYService {
|
|
|
4413
4573
|
return this.metricsTracker.getAll();
|
|
4414
4574
|
}
|
|
4415
4575
|
log(message) {
|
|
4416
|
-
|
|
4576
|
+
logger3.debug(`[PTYService] ${message}`);
|
|
4417
4577
|
}
|
|
4418
4578
|
}
|
|
4419
4579
|
|
|
@@ -4459,7 +4619,7 @@ var spawnAgentAction = {
|
|
|
4459
4619
|
validate: async (runtime, _message) => {
|
|
4460
4620
|
const ptyService = runtime.getService("PTY_SERVICE");
|
|
4461
4621
|
if (!ptyService) {
|
|
4462
|
-
|
|
4622
|
+
logger4.warn("[SPAWN_CODING_AGENT] PTYService not available");
|
|
4463
4623
|
return false;
|
|
4464
4624
|
}
|
|
4465
4625
|
return true;
|
|
@@ -4572,7 +4732,7 @@ var spawnAgentAction = {
|
|
|
4572
4732
|
ptyService.onSessionEvent((sessionId, event, data) => {
|
|
4573
4733
|
if (sessionId !== session.id)
|
|
4574
4734
|
return;
|
|
4575
|
-
|
|
4735
|
+
logger4.debug(`[Session ${sessionId}] ${event}: ${JSON.stringify(data)}`);
|
|
4576
4736
|
if (!coordinator) {
|
|
4577
4737
|
if (event === "blocked" && callback) {
|
|
4578
4738
|
callback({
|
|
@@ -4624,7 +4784,7 @@ var spawnAgentAction = {
|
|
|
4624
4784
|
};
|
|
4625
4785
|
} catch (error) {
|
|
4626
4786
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4627
|
-
|
|
4787
|
+
logger4.error("[SPAWN_CODING_AGENT] Failed to spawn agent:", errorMessage);
|
|
4628
4788
|
if (callback) {
|
|
4629
4789
|
await callback({
|
|
4630
4790
|
text: `Failed to spawn coding agent: ${errorMessage}`
|
|
@@ -4672,11 +4832,13 @@ var spawnAgentAction = {
|
|
|
4672
4832
|
|
|
4673
4833
|
// src/actions/coding-task-handlers.ts
|
|
4674
4834
|
import {
|
|
4675
|
-
logger as
|
|
4835
|
+
logger as logger6,
|
|
4676
4836
|
ModelType as ModelType5
|
|
4677
4837
|
} from "@elizaos/core";
|
|
4678
4838
|
// src/services/trajectory-feedback.ts
|
|
4839
|
+
import { logger as elizaLogger } from "@elizaos/core";
|
|
4679
4840
|
var QUERY_TIMEOUT_MS = 5000;
|
|
4841
|
+
var SLOW_PATH_BUDGET_MS = 15000;
|
|
4680
4842
|
function withTimeout2(promise, ms) {
|
|
4681
4843
|
return Promise.race([
|
|
4682
4844
|
promise,
|
|
@@ -4749,12 +4911,12 @@ async function queryPastExperience(runtime, options = {}) {
|
|
|
4749
4911
|
taskDescription,
|
|
4750
4912
|
repo
|
|
4751
4913
|
} = options;
|
|
4752
|
-
const
|
|
4753
|
-
if (!
|
|
4914
|
+
const logger5 = getTrajectoryLogger(runtime);
|
|
4915
|
+
if (!logger5)
|
|
4754
4916
|
return [];
|
|
4755
4917
|
const startDate = new Date(Date.now() - lookbackHours * 60 * 60 * 1000).toISOString();
|
|
4756
4918
|
try {
|
|
4757
|
-
const result = await withTimeout2(
|
|
4919
|
+
const result = await withTimeout2(logger5.listTrajectories({
|
|
4758
4920
|
source: "orchestrator",
|
|
4759
4921
|
limit: maxTrajectories,
|
|
4760
4922
|
startDate
|
|
@@ -4762,18 +4924,37 @@ async function queryPastExperience(runtime, options = {}) {
|
|
|
4762
4924
|
if (!result.trajectories || result.trajectories.length === 0)
|
|
4763
4925
|
return [];
|
|
4764
4926
|
const experiences = [];
|
|
4927
|
+
const slowPathDeadline = Date.now() + SLOW_PATH_BUDGET_MS;
|
|
4765
4928
|
const maxScans = Math.min(result.trajectories.length, maxTrajectories);
|
|
4766
4929
|
for (let scanIdx = 0;scanIdx < maxScans; scanIdx++) {
|
|
4767
4930
|
const summary = result.trajectories[scanIdx];
|
|
4768
|
-
const
|
|
4769
|
-
|
|
4770
|
-
continue;
|
|
4771
|
-
const metadata = detail.metadata;
|
|
4931
|
+
const metadata = summary.metadata;
|
|
4932
|
+
const metadataInsights = Array.isArray(metadata?.insights) ? metadata.insights.filter((value) => typeof value === "string" && value.trim().length > 0).slice(0, 50) : [];
|
|
4772
4933
|
const decisionType = metadata?.orchestrator?.decisionType ?? "unknown";
|
|
4773
4934
|
const taskLabel = metadata?.orchestrator?.taskLabel ?? "";
|
|
4774
4935
|
const trajectoryRepo = metadata?.orchestrator?.repo;
|
|
4775
4936
|
if (repo && (!trajectoryRepo || trajectoryRepo !== repo))
|
|
4776
4937
|
continue;
|
|
4938
|
+
if (metadataInsights.length > 0) {
|
|
4939
|
+
elizaLogger.debug(`[trajectory-feedback] Fast path: ${metadataInsights.length} insight(s) from metadata for ${summary.id}`);
|
|
4940
|
+
for (const insight of metadataInsights) {
|
|
4941
|
+
experiences.push({
|
|
4942
|
+
timestamp: summary.startTime,
|
|
4943
|
+
decisionType,
|
|
4944
|
+
taskLabel,
|
|
4945
|
+
insight
|
|
4946
|
+
});
|
|
4947
|
+
}
|
|
4948
|
+
continue;
|
|
4949
|
+
}
|
|
4950
|
+
if (Date.now() > slowPathDeadline) {
|
|
4951
|
+
elizaLogger.debug(`[trajectory-feedback] Slow path budget exhausted; stopping detail loads`);
|
|
4952
|
+
break;
|
|
4953
|
+
}
|
|
4954
|
+
elizaLogger.debug(`[trajectory-feedback] Slow path: loading full detail for ${summary.id} (no metadata insights)`);
|
|
4955
|
+
const detail = await withTimeout2(logger5.getTrajectoryDetail(summary.id), QUERY_TIMEOUT_MS).catch(() => null);
|
|
4956
|
+
if (!detail?.steps)
|
|
4957
|
+
continue;
|
|
4777
4958
|
for (const step of detail.steps) {
|
|
4778
4959
|
if (!step.llmCalls)
|
|
4779
4960
|
continue;
|
|
@@ -4806,7 +4987,7 @@ async function queryPastExperience(runtime, options = {}) {
|
|
|
4806
4987
|
}
|
|
4807
4988
|
return Array.from(seen.values()).sort((a, b) => b.timestamp - a.timestamp).slice(0, maxEntries);
|
|
4808
4989
|
} catch (err) {
|
|
4809
|
-
|
|
4990
|
+
elizaLogger.error(`[trajectory-feedback] Failed to query past experience: ${err}`);
|
|
4810
4991
|
return [];
|
|
4811
4992
|
}
|
|
4812
4993
|
}
|
|
@@ -4843,7 +5024,7 @@ import * as fs from "node:fs";
|
|
|
4843
5024
|
import * as os2 from "node:os";
|
|
4844
5025
|
import * as path3 from "node:path";
|
|
4845
5026
|
import {
|
|
4846
|
-
logger as
|
|
5027
|
+
logger as logger5
|
|
4847
5028
|
} from "@elizaos/core";
|
|
4848
5029
|
function createScratchDir() {
|
|
4849
5030
|
const baseDir = path3.join(os2.homedir(), ".milady", "workspaces");
|
|
@@ -4888,7 +5069,7 @@ ${preview}` : `Agent "${label}" completed the task.`
|
|
|
4888
5069
|
});
|
|
4889
5070
|
}
|
|
4890
5071
|
ptyService.stopSession(sessionId, true).catch((err) => {
|
|
4891
|
-
|
|
5072
|
+
logger5.warn(`[START_CODING_TASK] Failed to stop session for "${label}" after task complete: ${err}`);
|
|
4892
5073
|
});
|
|
4893
5074
|
}
|
|
4894
5075
|
if (event === "error" && callback) {
|
|
@@ -4901,7 +5082,7 @@ ${preview}` : `Agent "${label}" completed the task.`
|
|
|
4901
5082
|
const wsService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
4902
5083
|
if (wsService) {
|
|
4903
5084
|
wsService.removeScratchDir(scratchDir).catch((err) => {
|
|
4904
|
-
|
|
5085
|
+
logger5.warn(`[START_CODING_TASK] Failed to cleanup scratch dir for "${label}": ${err}`);
|
|
4905
5086
|
});
|
|
4906
5087
|
}
|
|
4907
5088
|
}
|
|
@@ -4988,7 +5169,7 @@ ${taskList}
|
|
|
4988
5169
|
}));
|
|
4989
5170
|
return result?.trim() || "";
|
|
4990
5171
|
} catch (err) {
|
|
4991
|
-
|
|
5172
|
+
logger6.warn(`Swarm context generation failed: ${err}`);
|
|
4992
5173
|
return "";
|
|
4993
5174
|
}
|
|
4994
5175
|
}
|
|
@@ -5160,7 +5341,7 @@ ${swarmContext}
|
|
|
5160
5341
|
}
|
|
5161
5342
|
} catch (error) {
|
|
5162
5343
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5163
|
-
|
|
5344
|
+
logger6.error(`[START_CODING_TASK] Failed to spawn agent ${i + 1}:`, errorMessage);
|
|
5164
5345
|
results.push({
|
|
5165
5346
|
sessionId: "",
|
|
5166
5347
|
agentType: specAgentType,
|
|
@@ -5192,7 +5373,7 @@ ${swarmContext}
|
|
|
5192
5373
|
};
|
|
5193
5374
|
}
|
|
5194
5375
|
async function handleSingleAgent(ctx, task) {
|
|
5195
|
-
|
|
5376
|
+
logger6.debug(`[START_CODING_TASK] handleSingleAgent called, agentType=${ctx.defaultAgentType}, task=${task ? "yes" : "none"}, repo=${ctx.repo ?? "none"}`);
|
|
5196
5377
|
const {
|
|
5197
5378
|
runtime,
|
|
5198
5379
|
ptyService,
|
|
@@ -5252,14 +5433,14 @@ async function handleSingleAgent(ctx, task) {
|
|
|
5252
5433
|
} else {
|
|
5253
5434
|
workdir = createScratchDir();
|
|
5254
5435
|
}
|
|
5255
|
-
|
|
5436
|
+
logger6.debug(`[START_CODING_TASK] Spawning ${agentType} agent, task: ${task ? `"${task.slice(0, 80)}..."` : "(none)"}, workdir: ${workdir}`);
|
|
5256
5437
|
try {
|
|
5257
5438
|
if (agentType !== "shell" && agentType !== "pi") {
|
|
5258
5439
|
const [preflight] = await ptyService.checkAvailableAgents([
|
|
5259
5440
|
agentType
|
|
5260
5441
|
]);
|
|
5261
5442
|
if (preflight && !preflight.installed) {
|
|
5262
|
-
|
|
5443
|
+
logger6.warn(`[START_CODING_TASK] ${preflight.adapter} CLI not installed`);
|
|
5263
5444
|
if (callback) {
|
|
5264
5445
|
await callback({
|
|
5265
5446
|
text: `${preflight.adapter} CLI is not installed.
|
|
@@ -5269,7 +5450,7 @@ Docs: ${preflight.docsUrl}`
|
|
|
5269
5450
|
}
|
|
5270
5451
|
return { success: false, error: "AGENT_NOT_INSTALLED" };
|
|
5271
5452
|
}
|
|
5272
|
-
|
|
5453
|
+
logger6.debug(`[START_CODING_TASK] Preflight OK: ${preflight?.adapter} installed`);
|
|
5273
5454
|
}
|
|
5274
5455
|
const piRequested = isPiAgentType(rawAgentType);
|
|
5275
5456
|
const initialTask = piRequested ? toPiCommand(task) : task;
|
|
@@ -5285,7 +5466,7 @@ Docs: ${preflight.docsUrl}`
|
|
|
5285
5466
|
|
|
5286
5467
|
`) || undefined;
|
|
5287
5468
|
const coordinator = getCoordinator(runtime);
|
|
5288
|
-
|
|
5469
|
+
logger6.debug(`[START_CODING_TASK] Calling spawnSession (${agentType}, coordinator=${!!coordinator})`);
|
|
5289
5470
|
const session = await ptyService.spawnSession({
|
|
5290
5471
|
name: `coding-${Date.now()}`,
|
|
5291
5472
|
agentType,
|
|
@@ -5304,7 +5485,7 @@ Docs: ${preflight.docsUrl}`
|
|
|
5304
5485
|
label
|
|
5305
5486
|
}
|
|
5306
5487
|
});
|
|
5307
|
-
|
|
5488
|
+
logger6.debug(`[START_CODING_TASK] Session spawned: ${session.id} (${session.status})`);
|
|
5308
5489
|
const isScratchWorkspace = !repo;
|
|
5309
5490
|
const scratchDir = isScratchWorkspace ? workdir : null;
|
|
5310
5491
|
registerSessionEvents(ptyService, runtime, session.id, label, scratchDir, callback, !!coordinator);
|
|
@@ -5345,7 +5526,7 @@ Session ID: ${session.id}` });
|
|
|
5345
5526
|
};
|
|
5346
5527
|
} catch (error) {
|
|
5347
5528
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5348
|
-
|
|
5529
|
+
logger6.error("[START_CODING_TASK] Failed to spawn agent:", errorMessage);
|
|
5349
5530
|
if (callback) {
|
|
5350
5531
|
await callback({
|
|
5351
5532
|
text: `Failed to start coding agent: ${errorMessage}`
|
|
@@ -5526,7 +5707,7 @@ var startCodingTaskAction = {
|
|
|
5526
5707
|
|
|
5527
5708
|
// src/actions/stop-agent.ts
|
|
5528
5709
|
import {
|
|
5529
|
-
logger as
|
|
5710
|
+
logger as logger7
|
|
5530
5711
|
} from "@elizaos/core";
|
|
5531
5712
|
var stopAgentAction = {
|
|
5532
5713
|
name: "STOP_CODING_AGENT",
|
|
@@ -5605,7 +5786,7 @@ var stopAgentAction = {
|
|
|
5605
5786
|
try {
|
|
5606
5787
|
await ptyService.stopSession(session2.id);
|
|
5607
5788
|
} catch (err) {
|
|
5608
|
-
|
|
5789
|
+
logger7.error(`Failed to stop session ${session2.id}: ${err}`);
|
|
5609
5790
|
}
|
|
5610
5791
|
}
|
|
5611
5792
|
if (state?.codingSession) {
|
|
@@ -6495,8 +6676,143 @@ class CodingWorkspaceService {
|
|
|
6495
6676
|
}
|
|
6496
6677
|
}
|
|
6497
6678
|
// src/api/agent-routes.ts
|
|
6679
|
+
import { access, readFile as readFile3, realpath, rm } from "node:fs/promises";
|
|
6680
|
+
import { createHash } from "node:crypto";
|
|
6498
6681
|
import * as os4 from "node:os";
|
|
6499
6682
|
import * as path6 from "node:path";
|
|
6683
|
+
import { execFile } from "node:child_process";
|
|
6684
|
+
import { promisify } from "node:util";
|
|
6685
|
+
var execFileAsync = promisify(execFile);
|
|
6686
|
+
var PREFLIGHT_DONE = new Set;
|
|
6687
|
+
var PREFLIGHT_INFLIGHT = new Map;
|
|
6688
|
+
function shouldAutoPreflight() {
|
|
6689
|
+
if (process.env.PARALLAX_BENCHMARK_PREFLIGHT_AUTO === "1")
|
|
6690
|
+
return true;
|
|
6691
|
+
return false;
|
|
6692
|
+
}
|
|
6693
|
+
function isPathInside(parent, candidate) {
|
|
6694
|
+
return candidate === parent || candidate.startsWith(`${parent}${path6.sep}`);
|
|
6695
|
+
}
|
|
6696
|
+
async function resolveSafeVenvPath(workdir, venvDirRaw) {
|
|
6697
|
+
const venvDir = venvDirRaw.trim();
|
|
6698
|
+
if (!venvDir) {
|
|
6699
|
+
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV must be non-empty");
|
|
6700
|
+
}
|
|
6701
|
+
if (path6.isAbsolute(venvDir)) {
|
|
6702
|
+
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV must be relative to workdir");
|
|
6703
|
+
}
|
|
6704
|
+
const normalized = path6.normalize(venvDir);
|
|
6705
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith(`..${path6.sep}`)) {
|
|
6706
|
+
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV must stay within workdir");
|
|
6707
|
+
}
|
|
6708
|
+
const workdirResolved = path6.resolve(workdir);
|
|
6709
|
+
const workdirReal = await realpath(workdirResolved);
|
|
6710
|
+
const resolved = path6.resolve(workdirReal, normalized);
|
|
6711
|
+
if (!isPathInside(workdirReal, resolved)) {
|
|
6712
|
+
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV resolves outside workdir");
|
|
6713
|
+
}
|
|
6714
|
+
if (resolved === workdirReal) {
|
|
6715
|
+
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV must not resolve to workdir root");
|
|
6716
|
+
}
|
|
6717
|
+
try {
|
|
6718
|
+
const resolvedReal = await realpath(resolved);
|
|
6719
|
+
if (!isPathInside(workdirReal, resolvedReal) || resolvedReal === workdirReal) {
|
|
6720
|
+
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV resolves outside workdir");
|
|
6721
|
+
}
|
|
6722
|
+
} catch (err) {
|
|
6723
|
+
const maybeErr = err;
|
|
6724
|
+
if (maybeErr?.code !== "ENOENT")
|
|
6725
|
+
throw err;
|
|
6726
|
+
const parentReal = await realpath(path6.dirname(resolved));
|
|
6727
|
+
if (!isPathInside(workdirReal, parentReal)) {
|
|
6728
|
+
throw new Error("PARALLAX_BENCHMARK_PREFLIGHT_VENV parent resolves outside workdir");
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
6731
|
+
return resolved;
|
|
6732
|
+
}
|
|
6733
|
+
async function fileExists(filePath) {
|
|
6734
|
+
try {
|
|
6735
|
+
await access(filePath);
|
|
6736
|
+
return true;
|
|
6737
|
+
} catch {
|
|
6738
|
+
return false;
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
async function resolveRequirementsPath(workdir) {
|
|
6742
|
+
const workdirReal = await realpath(path6.resolve(workdir));
|
|
6743
|
+
const candidates = [
|
|
6744
|
+
path6.join(workdir, "apps", "api", "requirements.txt"),
|
|
6745
|
+
path6.join(workdir, "requirements.txt")
|
|
6746
|
+
];
|
|
6747
|
+
for (const candidate of candidates) {
|
|
6748
|
+
if (!await fileExists(candidate))
|
|
6749
|
+
continue;
|
|
6750
|
+
try {
|
|
6751
|
+
const candidateReal = await realpath(candidate);
|
|
6752
|
+
if (isPathInside(workdirReal, candidateReal))
|
|
6753
|
+
return candidateReal;
|
|
6754
|
+
} catch {}
|
|
6755
|
+
}
|
|
6756
|
+
return null;
|
|
6757
|
+
}
|
|
6758
|
+
async function fingerprintRequirementsFile(requirementsPath) {
|
|
6759
|
+
const file = await readFile3(requirementsPath);
|
|
6760
|
+
return createHash("sha256").update(file).digest("hex");
|
|
6761
|
+
}
|
|
6762
|
+
async function runBenchmarkPreflight(workdir) {
|
|
6763
|
+
if (!shouldAutoPreflight())
|
|
6764
|
+
return;
|
|
6765
|
+
const requirementsPath = await resolveRequirementsPath(workdir);
|
|
6766
|
+
if (!requirementsPath)
|
|
6767
|
+
return;
|
|
6768
|
+
const requirementsFingerprint = await fingerprintRequirementsFile(requirementsPath);
|
|
6769
|
+
const mode = process.env.PARALLAX_BENCHMARK_PREFLIGHT_MODE?.toLowerCase() === "warm" ? "warm" : "cold";
|
|
6770
|
+
const venvDir = process.env.PARALLAX_BENCHMARK_PREFLIGHT_VENV || ".benchmark-venv";
|
|
6771
|
+
const venvPath = await resolveSafeVenvPath(workdir, venvDir);
|
|
6772
|
+
const pythonInVenv = path6.join(venvPath, process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "python.exe" : "python");
|
|
6773
|
+
const key = `${workdir}::${mode}::${venvPath}::${requirementsFingerprint}`;
|
|
6774
|
+
if (PREFLIGHT_DONE.has(key)) {
|
|
6775
|
+
if (await fileExists(pythonInVenv))
|
|
6776
|
+
return;
|
|
6777
|
+
PREFLIGHT_DONE.delete(key);
|
|
6778
|
+
}
|
|
6779
|
+
const existing = PREFLIGHT_INFLIGHT.get(key);
|
|
6780
|
+
if (existing) {
|
|
6781
|
+
await existing;
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
const run = (async () => {
|
|
6785
|
+
const pythonCommand = process.platform === "win32" ? "python" : "python3";
|
|
6786
|
+
if (mode === "cold") {
|
|
6787
|
+
await rm(venvPath, { recursive: true, force: true });
|
|
6788
|
+
}
|
|
6789
|
+
const hasVenv = await fileExists(pythonInVenv);
|
|
6790
|
+
if (!hasVenv) {
|
|
6791
|
+
await execFileAsync(pythonCommand, ["-m", "venv", venvPath], {
|
|
6792
|
+
cwd: workdir,
|
|
6793
|
+
timeout: 120000,
|
|
6794
|
+
maxBuffer: 8 * 1024 * 1024
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6797
|
+
await execFileAsync(pythonInVenv, ["-m", "pip", "install", "--upgrade", "pip"], {
|
|
6798
|
+
cwd: workdir,
|
|
6799
|
+
timeout: 300000,
|
|
6800
|
+
maxBuffer: 8 * 1024 * 1024
|
|
6801
|
+
});
|
|
6802
|
+
await execFileAsync(pythonInVenv, ["-m", "pip", "install", "-r", requirementsPath], {
|
|
6803
|
+
cwd: workdir,
|
|
6804
|
+
timeout: 600000,
|
|
6805
|
+
maxBuffer: 16 * 1024 * 1024
|
|
6806
|
+
});
|
|
6807
|
+
PREFLIGHT_DONE.add(key);
|
|
6808
|
+
})();
|
|
6809
|
+
PREFLIGHT_INFLIGHT.set(key, run);
|
|
6810
|
+
try {
|
|
6811
|
+
await run;
|
|
6812
|
+
} finally {
|
|
6813
|
+
PREFLIGHT_INFLIGHT.delete(key);
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6500
6816
|
async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
6501
6817
|
const method = req.method?.toUpperCase();
|
|
6502
6818
|
if (method === "GET" && pathname === "/api/coding-agents/preflight") {
|
|
@@ -6624,19 +6940,25 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
6624
6940
|
metadata
|
|
6625
6941
|
} = body;
|
|
6626
6942
|
const workspaceBaseDir = path6.join(os4.homedir(), ".milady", "workspaces");
|
|
6627
|
-
const
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6943
|
+
const workspaceBaseDirResolved = path6.resolve(workspaceBaseDir);
|
|
6944
|
+
const cwdResolved = path6.resolve(process.cwd());
|
|
6945
|
+
const workspaceBaseDirReal = await realpath(workspaceBaseDirResolved).catch(() => workspaceBaseDirResolved);
|
|
6946
|
+
const cwdReal = await realpath(cwdResolved).catch(() => cwdResolved);
|
|
6947
|
+
const allowedPrefixes = [workspaceBaseDirReal, cwdReal];
|
|
6631
6948
|
let workdir = rawWorkdir;
|
|
6632
6949
|
if (workdir) {
|
|
6633
6950
|
const resolved = path6.resolve(workdir);
|
|
6634
|
-
const
|
|
6951
|
+
const resolvedReal = await realpath(resolved).catch(() => null);
|
|
6952
|
+
if (!resolvedReal) {
|
|
6953
|
+
sendError(res, "workdir must exist", 403);
|
|
6954
|
+
return true;
|
|
6955
|
+
}
|
|
6956
|
+
const isAllowed = allowedPrefixes.some((prefix2) => resolvedReal === prefix2 || resolvedReal.startsWith(prefix2 + path6.sep));
|
|
6635
6957
|
if (!isAllowed) {
|
|
6636
6958
|
sendError(res, "workdir must be within workspace base directory or cwd", 403);
|
|
6637
6959
|
return true;
|
|
6638
6960
|
}
|
|
6639
|
-
workdir =
|
|
6961
|
+
workdir = resolvedReal;
|
|
6640
6962
|
}
|
|
6641
6963
|
const activeSessions = await ctx.ptyService.listSessions();
|
|
6642
6964
|
const maxSessions = 8;
|
|
@@ -6644,6 +6966,13 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
6644
6966
|
sendError(res, `Concurrent session limit reached (${maxSessions})`, 429);
|
|
6645
6967
|
return true;
|
|
6646
6968
|
}
|
|
6969
|
+
if (workdir) {
|
|
6970
|
+
try {
|
|
6971
|
+
await runBenchmarkPreflight(workdir);
|
|
6972
|
+
} catch (preflightError) {
|
|
6973
|
+
console.warn(`[coding-agent] benchmark preflight failed for ${workdir}:`, preflightError);
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6647
6976
|
const credentials = {
|
|
6648
6977
|
anthropicKey: ctx.runtime.getSetting("ANTHROPIC_API_KEY"),
|
|
6649
6978
|
openaiKey: ctx.runtime.getSetting("OPENAI_API_KEY"),
|
|
@@ -7370,5 +7699,5 @@ export {
|
|
|
7370
7699
|
CodingWorkspaceService
|
|
7371
7700
|
};
|
|
7372
7701
|
|
|
7373
|
-
//# debugId=
|
|
7702
|
+
//# debugId=A818F3DE8DCF48E664756E2164756E21
|
|
7374
7703
|
//# sourceMappingURL=index.js.map
|