@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/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
- checkAllTasksComplete: () => checkAllTasksComplete
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
- sessionId: t.sessionId,
737
- label: t.label,
738
- agentType: t.agentType,
739
- originalTask: t.originalTask,
740
- status: t.status,
741
- completionSummary: t.completionSummary ?? ""
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 logger3
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 logger2 } from "@elizaos/core";
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 (event !== "stopped" && event !== "error") {
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
- logger2.info("[PTYService] SwarmCoordinator already registered, skipping duplicate start");
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
- logger2.info("[PTYService] SwarmCoordinator wired and started");
4011
+ logger3.info("[PTYService] SwarmCoordinator wired and started");
3876
4012
  } catch (err) {
3877
- logger2.error(`[PTYService] Failed to wire SwarmCoordinator: ${err}`);
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
- logger2.debug(`[PTYService] Hook event for ${sessionId}: ${event} ${summary}`);
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) => logger2.debug(`[PTYService] Failed to forward hook event to session: ${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
- logger2.debug(`[PTYService] ${message}`);
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
- logger3.warn("[SPAWN_CODING_AGENT] PTYService not available");
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
- logger3.debug(`[Session ${sessionId}] ${event}: ${JSON.stringify(data)}`);
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
- logger3.error("[SPAWN_CODING_AGENT] Failed to spawn agent:", errorMessage);
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 logger5,
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 logger4 = getTrajectoryLogger(runtime);
4753
- if (!logger4)
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(logger4.listTrajectories({
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 detail = await withTimeout2(logger4.getTrajectoryDetail(summary.id), QUERY_TIMEOUT_MS).catch(() => null);
4769
- if (!detail?.steps)
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
- console.error("[trajectory-feedback] Failed to query past experience:", err);
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 logger4
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
- logger4.warn(`[START_CODING_TASK] Failed to stop session for "${label}" after task complete: ${err}`);
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
- logger4.warn(`[START_CODING_TASK] Failed to cleanup scratch dir for "${label}": ${err}`);
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
- logger5.warn(`Swarm context generation failed: ${err}`);
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
- logger5.error(`[START_CODING_TASK] Failed to spawn agent ${i + 1}:`, errorMessage);
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
- logger5.debug(`[START_CODING_TASK] handleSingleAgent called, agentType=${ctx.defaultAgentType}, task=${task ? "yes" : "none"}, repo=${ctx.repo ?? "none"}`);
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
- logger5.debug(`[START_CODING_TASK] Spawning ${agentType} agent, task: ${task ? `"${task.slice(0, 80)}..."` : "(none)"}, workdir: ${workdir}`);
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
- logger5.warn(`[START_CODING_TASK] ${preflight.adapter} CLI not installed`);
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
- logger5.debug(`[START_CODING_TASK] Preflight OK: ${preflight?.adapter} installed`);
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
- logger5.debug(`[START_CODING_TASK] Calling spawnSession (${agentType}, coordinator=${!!coordinator})`);
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
- logger5.debug(`[START_CODING_TASK] Session spawned: ${session.id} (${session.status})`);
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
- logger5.error("[START_CODING_TASK] Failed to spawn agent:", errorMessage);
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 logger6
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
- logger6.error(`Failed to stop session ${session2.id}: ${err}`);
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 allowedPrefixes = [
6628
- path6.resolve(workspaceBaseDir),
6629
- path6.resolve(process.cwd())
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 isAllowed = allowedPrefixes.some((prefix2) => resolved === prefix2 || resolved.startsWith(prefix2 + path6.sep));
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 = resolved;
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=1AF5CF89CDA1169464756E2164756E21
7702
+ //# debugId=A818F3DE8DCF48E664756E2164756E21
7374
7703
  //# sourceMappingURL=index.js.map