@elizaos/plugin-agent-orchestrator 0.3.12 → 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 +253 -22
- package/dist/index.js.map +7 -7
- 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 +2 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-routes.d.ts","sourceRoot":"","sources":["../../src/api/agent-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-routes.d.ts","sourceRoot":"","sources":["../../src/api/agent-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAajE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiMhD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,OAAO,CAAC,CAuelB"}
|
package/dist/index.js
CHANGED
|
@@ -537,6 +537,7 @@ __export(exports_swarm_decision_loop, {
|
|
|
537
537
|
handleBlocked: () => handleBlocked,
|
|
538
538
|
handleAutonomousDecision: () => handleAutonomousDecision,
|
|
539
539
|
executeDecision: () => executeDecision,
|
|
540
|
+
clearDeferredTurnCompleteTimers: () => clearDeferredTurnCompleteTimers,
|
|
540
541
|
checkAllTasksComplete: () => checkAllTasksComplete,
|
|
541
542
|
POST_SEND_COOLDOWN_MS: () => POST_SEND_COOLDOWN_MS
|
|
542
543
|
});
|
|
@@ -554,6 +555,12 @@ function withTimeout(promise, ms, label) {
|
|
|
554
555
|
});
|
|
555
556
|
});
|
|
556
557
|
}
|
|
558
|
+
function clearDeferredTurnCompleteTimers() {
|
|
559
|
+
for (const timer of deferredTurnCompleteTimers.values()) {
|
|
560
|
+
clearTimeout(timer);
|
|
561
|
+
}
|
|
562
|
+
deferredTurnCompleteTimers.clear();
|
|
563
|
+
}
|
|
557
564
|
function toContextSummary(taskCtx) {
|
|
558
565
|
return {
|
|
559
566
|
sessionId: taskCtx.sessionId,
|
|
@@ -733,14 +740,23 @@ function checkAllTasksComplete(ctx) {
|
|
|
733
740
|
};
|
|
734
741
|
if (swarmCompleteCb) {
|
|
735
742
|
ctx.log("checkAllTasksComplete: swarm complete callback is wired — calling synthesis");
|
|
736
|
-
const taskSummaries = tasks.map((t) =>
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
+
});
|
|
744
760
|
withTimeout(Promise.resolve().then(() => swarmCompleteCb({
|
|
745
761
|
tasks: taskSummaries,
|
|
746
762
|
total: tasks.length,
|
|
@@ -958,6 +974,8 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
958
974
|
}
|
|
959
975
|
}
|
|
960
976
|
async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
977
|
+
if (taskCtx.status !== "active")
|
|
978
|
+
return;
|
|
961
979
|
if (ctx.inFlightDecisions.has(sessionId)) {
|
|
962
980
|
ctx.log(`Buffering turn-complete for ${sessionId} (in-flight decision running)`);
|
|
963
981
|
ctx.pendingTurnComplete.set(sessionId, data);
|
|
@@ -966,10 +984,35 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
966
984
|
if (taskCtx.lastInputSentAt) {
|
|
967
985
|
const elapsed = Date.now() - taskCtx.lastInputSentAt;
|
|
968
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
|
+
}
|
|
969
1006
|
ctx.log(`Suppressing turn-complete for "${taskCtx.label}" — ` + `${Math.round(elapsed / 1000)}s since last input (cooldown ${POST_SEND_COOLDOWN_MS / 1000}s)`);
|
|
970
1007
|
return;
|
|
971
1008
|
}
|
|
972
1009
|
}
|
|
1010
|
+
const deferredTimer = deferredTurnCompleteTimers.get(sessionId);
|
|
1011
|
+
if (deferredTimer) {
|
|
1012
|
+
clearTimeout(deferredTimer);
|
|
1013
|
+
deferredTurnCompleteTimers.delete(sessionId);
|
|
1014
|
+
}
|
|
1015
|
+
ctx.pendingTurnComplete.delete(sessionId);
|
|
973
1016
|
ctx.inFlightDecisions.add(sessionId);
|
|
974
1017
|
try {
|
|
975
1018
|
ctx.log(`Turn complete for "${taskCtx.label}" — assessing whether task is done`);
|
|
@@ -1219,10 +1262,11 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
1219
1262
|
await drainPendingBlocked(ctx, sessionId);
|
|
1220
1263
|
}
|
|
1221
1264
|
}
|
|
1222
|
-
var DECISION_CB_TIMEOUT_MS = 30000, MAX_AUTO_RESPONSES = 10, POST_SEND_COOLDOWN_MS = 15000;
|
|
1265
|
+
var DECISION_CB_TIMEOUT_MS = 30000, MAX_AUTO_RESPONSES = 10, POST_SEND_COOLDOWN_MS = 15000, deferredTurnCompleteTimers;
|
|
1223
1266
|
var init_swarm_decision_loop = __esm(() => {
|
|
1224
1267
|
init_ansi_utils();
|
|
1225
1268
|
init_swarm_event_triage();
|
|
1269
|
+
deferredTurnCompleteTimers = new Map;
|
|
1226
1270
|
});
|
|
1227
1271
|
|
|
1228
1272
|
// src/actions/finalize-workspace.ts
|
|
@@ -3177,6 +3221,7 @@ async function scanIdleSessions(ctx) {
|
|
|
3177
3221
|
if (!session) {
|
|
3178
3222
|
ctx.log(`Idle watchdog: "${taskCtx.label}" — PTY session no longer exists, marking as stopped`);
|
|
3179
3223
|
taskCtx.status = "stopped";
|
|
3224
|
+
taskCtx.stoppedAt = now;
|
|
3180
3225
|
taskCtx.decisions.push({
|
|
3181
3226
|
timestamp: now,
|
|
3182
3227
|
event: "idle_watchdog",
|
|
@@ -3220,6 +3265,7 @@ async function scanIdleSessions(ctx) {
|
|
|
3220
3265
|
if (taskCtx.idleCheckCount >= MAX_IDLE_CHECKS) {
|
|
3221
3266
|
ctx.log(`Idle watchdog: force-stopping "${taskCtx.label}" after ${MAX_IDLE_CHECKS} checks`);
|
|
3222
3267
|
taskCtx.status = "stopped";
|
|
3268
|
+
taskCtx.stoppedAt = now;
|
|
3223
3269
|
taskCtx.decisions.push({
|
|
3224
3270
|
timestamp: now,
|
|
3225
3271
|
event: "idle_watchdog",
|
|
@@ -3354,6 +3400,7 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
3354
3400
|
var UNREGISTERED_BUFFER_MS = 2000;
|
|
3355
3401
|
var IDLE_SCAN_INTERVAL_MS = 60 * 1000;
|
|
3356
3402
|
var PAUSE_TIMEOUT_MS = 30000;
|
|
3403
|
+
var STOPPED_RECOVERY_WINDOW_MS = 90000;
|
|
3357
3404
|
|
|
3358
3405
|
class SwarmCoordinator {
|
|
3359
3406
|
static serviceType = "SWARM_COORDINATOR";
|
|
@@ -3454,6 +3501,7 @@ class SwarmCoordinator {
|
|
|
3454
3501
|
this.pendingDecisions.clear();
|
|
3455
3502
|
this.inFlightDecisions.clear();
|
|
3456
3503
|
this.pendingTurnComplete.clear();
|
|
3504
|
+
clearDeferredTurnCompleteTimers();
|
|
3457
3505
|
this.lastBlockedPromptFingerprint.clear();
|
|
3458
3506
|
this.pendingBlocked.clear();
|
|
3459
3507
|
this.unregisteredBuffer.clear();
|
|
@@ -3635,8 +3683,21 @@ class SwarmCoordinator {
|
|
|
3635
3683
|
}
|
|
3636
3684
|
return;
|
|
3637
3685
|
}
|
|
3686
|
+
let recoveredFromStopped = false;
|
|
3638
3687
|
if (taskCtx.status === "stopped" || taskCtx.status === "error" || taskCtx.status === "completed") {
|
|
3639
|
-
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") {
|
|
3640
3701
|
this.log(`Ignoring "${event}" for ${taskCtx.label} (status: ${taskCtx.status})`);
|
|
3641
3702
|
return;
|
|
3642
3703
|
}
|
|
@@ -3687,6 +3748,7 @@ class SwarmCoordinator {
|
|
|
3687
3748
|
case "stopped":
|
|
3688
3749
|
if (taskCtx.status !== "completed" && taskCtx.status !== "error") {
|
|
3689
3750
|
taskCtx.status = "stopped";
|
|
3751
|
+
taskCtx.stoppedAt = Date.now();
|
|
3690
3752
|
}
|
|
3691
3753
|
this.inFlightDecisions.delete(sessionId);
|
|
3692
3754
|
this.broadcast({
|
|
@@ -4774,7 +4836,9 @@ import {
|
|
|
4774
4836
|
ModelType as ModelType5
|
|
4775
4837
|
} from "@elizaos/core";
|
|
4776
4838
|
// src/services/trajectory-feedback.ts
|
|
4839
|
+
import { logger as elizaLogger } from "@elizaos/core";
|
|
4777
4840
|
var QUERY_TIMEOUT_MS = 5000;
|
|
4841
|
+
var SLOW_PATH_BUDGET_MS = 15000;
|
|
4778
4842
|
function withTimeout2(promise, ms) {
|
|
4779
4843
|
return Promise.race([
|
|
4780
4844
|
promise,
|
|
@@ -4860,18 +4924,37 @@ async function queryPastExperience(runtime, options = {}) {
|
|
|
4860
4924
|
if (!result.trajectories || result.trajectories.length === 0)
|
|
4861
4925
|
return [];
|
|
4862
4926
|
const experiences = [];
|
|
4927
|
+
const slowPathDeadline = Date.now() + SLOW_PATH_BUDGET_MS;
|
|
4863
4928
|
const maxScans = Math.min(result.trajectories.length, maxTrajectories);
|
|
4864
4929
|
for (let scanIdx = 0;scanIdx < maxScans; scanIdx++) {
|
|
4865
4930
|
const summary = result.trajectories[scanIdx];
|
|
4866
|
-
const
|
|
4867
|
-
|
|
4868
|
-
continue;
|
|
4869
|
-
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) : [];
|
|
4870
4933
|
const decisionType = metadata?.orchestrator?.decisionType ?? "unknown";
|
|
4871
4934
|
const taskLabel = metadata?.orchestrator?.taskLabel ?? "";
|
|
4872
4935
|
const trajectoryRepo = metadata?.orchestrator?.repo;
|
|
4873
4936
|
if (repo && (!trajectoryRepo || trajectoryRepo !== repo))
|
|
4874
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;
|
|
4875
4958
|
for (const step of detail.steps) {
|
|
4876
4959
|
if (!step.llmCalls)
|
|
4877
4960
|
continue;
|
|
@@ -4904,7 +4987,7 @@ async function queryPastExperience(runtime, options = {}) {
|
|
|
4904
4987
|
}
|
|
4905
4988
|
return Array.from(seen.values()).sort((a, b) => b.timestamp - a.timestamp).slice(0, maxEntries);
|
|
4906
4989
|
} catch (err) {
|
|
4907
|
-
|
|
4990
|
+
elizaLogger.error(`[trajectory-feedback] Failed to query past experience: ${err}`);
|
|
4908
4991
|
return [];
|
|
4909
4992
|
}
|
|
4910
4993
|
}
|
|
@@ -6593,8 +6676,143 @@ class CodingWorkspaceService {
|
|
|
6593
6676
|
}
|
|
6594
6677
|
}
|
|
6595
6678
|
// src/api/agent-routes.ts
|
|
6679
|
+
import { access, readFile as readFile3, realpath, rm } from "node:fs/promises";
|
|
6680
|
+
import { createHash } from "node:crypto";
|
|
6596
6681
|
import * as os4 from "node:os";
|
|
6597
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
|
+
}
|
|
6598
6816
|
async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
6599
6817
|
const method = req.method?.toUpperCase();
|
|
6600
6818
|
if (method === "GET" && pathname === "/api/coding-agents/preflight") {
|
|
@@ -6722,19 +6940,25 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
6722
6940
|
metadata
|
|
6723
6941
|
} = body;
|
|
6724
6942
|
const workspaceBaseDir = path6.join(os4.homedir(), ".milady", "workspaces");
|
|
6725
|
-
const
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
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];
|
|
6729
6948
|
let workdir = rawWorkdir;
|
|
6730
6949
|
if (workdir) {
|
|
6731
6950
|
const resolved = path6.resolve(workdir);
|
|
6732
|
-
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));
|
|
6733
6957
|
if (!isAllowed) {
|
|
6734
6958
|
sendError(res, "workdir must be within workspace base directory or cwd", 403);
|
|
6735
6959
|
return true;
|
|
6736
6960
|
}
|
|
6737
|
-
workdir =
|
|
6961
|
+
workdir = resolvedReal;
|
|
6738
6962
|
}
|
|
6739
6963
|
const activeSessions = await ctx.ptyService.listSessions();
|
|
6740
6964
|
const maxSessions = 8;
|
|
@@ -6742,6 +6966,13 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
6742
6966
|
sendError(res, `Concurrent session limit reached (${maxSessions})`, 429);
|
|
6743
6967
|
return true;
|
|
6744
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
|
+
}
|
|
6745
6976
|
const credentials = {
|
|
6746
6977
|
anthropicKey: ctx.runtime.getSetting("ANTHROPIC_API_KEY"),
|
|
6747
6978
|
openaiKey: ctx.runtime.getSetting("OPENAI_API_KEY"),
|
|
@@ -7468,5 +7699,5 @@ export {
|
|
|
7468
7699
|
CodingWorkspaceService
|
|
7469
7700
|
};
|
|
7470
7701
|
|
|
7471
|
-
//# debugId=
|
|
7702
|
+
//# debugId=A818F3DE8DCF48E664756E2164756E21
|
|
7472
7703
|
//# sourceMappingURL=index.js.map
|