plugin-agent-orchestrator 1.0.19 → 1.0.21

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.
Files changed (100) hide show
  1. package/dist/client/hooks/useRunEventStream.d.ts +22 -0
  2. package/dist/client/index.d.ts +1 -0
  3. package/dist/client/index.js +1 -1
  4. package/dist/externalVersion.js +6 -6
  5. package/dist/server/collections/agent-execution-spans.js +24 -0
  6. package/dist/server/collections/agent-loop-runs.js +36 -0
  7. package/dist/server/collections/orchestrator-config.js +14 -0
  8. package/dist/server/migrations/20260601000000-add-token-fields.d.ts +7 -0
  9. package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
  10. package/dist/server/plugin.js +47 -0
  11. package/dist/server/resources/agent-loop.js +33 -25
  12. package/dist/server/resources/tracing.js +5 -8
  13. package/dist/server/services/AgentHarness.d.ts +2 -0
  14. package/dist/server/services/AgentHarness.js +56 -90
  15. package/dist/server/services/AgentLoopController.d.ts +33 -20
  16. package/dist/server/services/AgentLoopController.js +164 -125
  17. package/dist/server/services/AgentLoopRepository.js +16 -34
  18. package/dist/server/services/AgentLoopService.d.ts +28 -18
  19. package/dist/server/services/AgentLoopService.js +7 -1
  20. package/dist/server/services/AgentPlannerService.js +5 -25
  21. package/dist/server/services/AgentRegistryService.d.ts +8 -0
  22. package/dist/server/services/AgentRegistryService.js +34 -24
  23. package/dist/server/services/CircuitBreaker.d.ts +40 -0
  24. package/dist/server/services/CircuitBreaker.js +120 -0
  25. package/dist/server/services/ContextAggregator.d.ts +45 -0
  26. package/dist/server/services/ContextAggregator.js +201 -0
  27. package/dist/server/services/ExecutionSpanService.js +2 -5
  28. package/dist/server/services/RunEventBus.d.ts +9 -0
  29. package/dist/server/services/RunEventBus.js +73 -0
  30. package/dist/server/services/TokenTracker.d.ts +62 -0
  31. package/dist/server/services/TokenTracker.js +173 -0
  32. package/dist/server/skill-hub/plugin.js +6 -6
  33. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +6 -6
  34. package/dist/server/tools/agent-loop.d.ts +8 -8
  35. package/dist/server/tools/agent-loop.js +30 -63
  36. package/dist/server/tools/delegate-task.js +14 -72
  37. package/dist/server/tools/orchestrator-plan.d.ts +6 -6
  38. package/dist/server/tools/orchestrator-plan.js +10 -47
  39. package/dist/server/types.d.ts +47 -0
  40. package/dist/server/types.js +24 -0
  41. package/dist/server/utils/ctx-utils.d.ts +30 -0
  42. package/dist/server/utils/ctx-utils.js +152 -0
  43. package/dist/server/utils/logging.d.ts +6 -0
  44. package/dist/server/utils/logging.js +86 -0
  45. package/package.json +44 -44
  46. package/src/client/AgentRunsTab.tsx +764 -764
  47. package/src/client/HarnessProfilesTab.tsx +247 -247
  48. package/src/client/OrchestratorSettings.tsx +106 -106
  49. package/src/client/RulesTab.tsx +716 -716
  50. package/src/client/hooks/useRunEventStream.ts +76 -0
  51. package/src/client/index.tsx +2 -1
  52. package/src/client/plugin.tsx +27 -27
  53. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  54. package/src/client/skill-hub/index.tsx +51 -51
  55. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
  56. package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
  57. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  58. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
  59. package/src/client/tools/PlanApprovalCard.tsx +175 -175
  60. package/src/client/tools/registerOrchestratorCards.ts +7 -7
  61. package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
  62. package/src/server/__tests__/circuit-breaker.test.ts +169 -0
  63. package/src/server/__tests__/context-aggregator.test.ts +222 -0
  64. package/src/server/__tests__/parallel-execution.test.ts +318 -0
  65. package/src/server/__tests__/smoke.test.ts +120 -0
  66. package/src/server/collections/agent-execution-spans.ts +24 -0
  67. package/src/server/collections/agent-harness-profiles.ts +59 -59
  68. package/src/server/collections/agent-loop-events.ts +71 -71
  69. package/src/server/collections/agent-loop-runs.ts +38 -1
  70. package/src/server/collections/agent-loop-steps.ts +144 -144
  71. package/src/server/collections/orchestrator-config.ts +14 -0
  72. package/src/server/collections/skill-executions.ts +106 -106
  73. package/src/server/collections/skill-loop-configs.ts +65 -65
  74. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  75. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
  76. package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
  77. package/src/server/plugin.ts +53 -0
  78. package/src/server/resources/agent-loop.ts +21 -12
  79. package/src/server/resources/tracing.ts +3 -7
  80. package/src/server/services/AgentHarness.ts +78 -116
  81. package/src/server/services/AgentLoopController.ts +197 -122
  82. package/src/server/services/AgentLoopRepository.ts +9 -25
  83. package/src/server/services/AgentLoopService.ts +13 -1
  84. package/src/server/services/AgentPlanValidator.ts +73 -73
  85. package/src/server/services/AgentPlannerService.ts +2 -25
  86. package/src/server/services/AgentRegistryService.ts +40 -31
  87. package/src/server/services/CircuitBreaker.ts +116 -0
  88. package/src/server/services/ContextAggregator.ts +239 -0
  89. package/src/server/services/ExecutionSpanService.ts +2 -4
  90. package/src/server/services/RunEventBus.ts +45 -0
  91. package/src/server/services/TokenTracker.ts +209 -0
  92. package/src/server/skill-hub/plugin.ts +898 -897
  93. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -458
  94. package/src/server/tools/agent-loop.ts +18 -57
  95. package/src/server/tools/delegate-task.ts +11 -93
  96. package/src/server/tools/orchestrator-plan.ts +26 -50
  97. package/src/server/tools/skill-execute.ts +160 -160
  98. package/src/server/types.ts +55 -0
  99. package/src/server/utils/ctx-utils.ts +118 -0
  100. package/src/server/utils/logging.ts +63 -0
@@ -29,13 +29,20 @@ __export(AgentLoopController_exports, {
29
29
  AgentLoopController: () => AgentLoopController
30
30
  });
31
31
  module.exports = __toCommonJS(AgentLoopController_exports);
32
+ var import_CircuitBreaker = require("./CircuitBreaker");
32
33
  var import_crypto = require("crypto");
34
+ var import_ctx_utils = require("../utils/ctx-utils");
33
35
  const DEFAULT_POLICY = {
34
36
  maxIterations: 20,
35
37
  maxStepAttempts: 2,
36
38
  allowReplan: true,
37
39
  requireVerification: true,
38
- stopOnApprovalRequired: true
40
+ stopOnApprovalRequired: true,
41
+ maxContextTokens: 4e3,
42
+ contextSummaryStrategy: "last_n",
43
+ includeToolResults: false,
44
+ includeStepOutputs: true,
45
+ maxConcurrency: 5
39
46
  };
40
47
  const TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "rejected", "canceled"]);
41
48
  const TERMINAL_STEP_STATUSES = /* @__PURE__ */ new Set(["succeeded", "skipped"]);
@@ -54,50 +61,21 @@ function normalizePolicy(policy) {
54
61
  next.allowReplan = next.allowReplan !== false;
55
62
  next.requireVerification = next.requireVerification !== false;
56
63
  next.stopOnApprovalRequired = next.stopOnApprovalRequired !== false;
64
+ next.maxContextTokens = next.maxContextTokens || DEFAULT_POLICY.maxContextTokens;
65
+ next.contextSummaryStrategy = next.contextSummaryStrategy || DEFAULT_POLICY.contextSummaryStrategy;
66
+ next.includeToolResults = next.includeToolResults !== false;
67
+ next.includeStepOutputs = next.includeStepOutputs !== false;
68
+ next.maxConcurrency = Math.max(1, Number(next.maxConcurrency || DEFAULT_POLICY.maxConcurrency));
57
69
  return next;
58
70
  }
59
- function asArray(value) {
60
- return Array.isArray(value) ? value : [];
61
- }
62
- function asObject(value) {
63
- if (value && typeof value === "object" && !Array.isArray(value)) return value;
64
- if (typeof value === "string" && value.trim()) {
65
- try {
66
- const parsed = JSON.parse(value);
67
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
68
- } catch {
69
- return {};
70
- }
71
- }
72
- return {};
73
- }
74
- function trimText(value, max = 5e4) {
75
- let text = "";
76
- if (typeof value === "string") {
77
- text = value;
78
- } else if (value != null) {
79
- try {
80
- text = JSON.stringify(value);
81
- } catch {
82
- text = String(value);
83
- }
84
- }
85
- return text.length > max ? `${text.slice(0, max)}
86
- ...[truncated]` : text;
87
- }
88
- function normalizeStepType(value) {
89
- return ["reasoning", "skill", "tool", "sub_agent", "verification"].includes(value) ? value : "tool";
90
- }
91
- function normalizePlanKey(step, index) {
92
- return String(step.planKey || step.key || step.id || `step_${index + 1}`);
93
- }
94
71
  class AgentLoopController {
95
- constructor(registryService, plannerService, validator, repository, harness) {
72
+ constructor(registryService, plannerService, validator, repository, harness, tokenTracker = null) {
96
73
  this.registryService = registryService;
97
74
  this.plannerService = plannerService;
98
75
  this.validator = validator;
99
76
  this.repository = repository;
100
77
  this.harness = harness;
78
+ this.tokenTracker = tokenTracker;
101
79
  }
102
80
  async createRun(options) {
103
81
  const goal = String(options.goal || "").trim();
@@ -144,7 +122,7 @@ class AgentLoopController {
144
122
  this.validator.validate(plan);
145
123
  const harnessTag = String(options.harnessTag || ((_a = options.metadata) == null ? void 0 : _a.harnessTag) || "default").trim() || "default";
146
124
  const harnessProfile = await this.registryService.getHarnessProfile(harnessTag);
147
- const harnessSettings = asObject(harnessProfile == null ? void 0 : harnessProfile.settings);
125
+ const harnessSettings = (0, import_ctx_utils.asObject)(harnessProfile == null ? void 0 : harnessProfile.settings);
148
126
  if (options.runId) {
149
127
  return this.revisePlanGoal(options.runId, plan, {
150
128
  goal: options.goal,
@@ -165,7 +143,7 @@ class AgentLoopController {
165
143
  userId: options.userId,
166
144
  policy: options.policy,
167
145
  metadata: {
168
- ...asObject(options.metadata),
146
+ ...(0, import_ctx_utils.asObject)(options.metadata),
169
147
  harnessTag,
170
148
  harnessProfileId: harnessProfile == null ? void 0 : harnessProfile.id,
171
149
  harnessSettings,
@@ -198,11 +176,11 @@ class AgentLoopController {
198
176
  planVersion: 1,
199
177
  harnessTag,
200
178
  steps: plan.map((step, index) => ({
201
- planKey: normalizePlanKey(step, index),
179
+ planKey: (0, import_ctx_utils.normalizePlanKey)(step, index),
202
180
  title: step.title || `Step ${index + 1}`,
203
- type: normalizeStepType(step.type),
181
+ type: (0, import_ctx_utils.normalizeStepType)(step.type),
204
182
  target: step.target || "",
205
- dependsOn: asArray(step.dependsOn).map(String)
183
+ dependsOn: (0, import_ctx_utils.asArray)(step.dependsOn).map(String)
206
184
  }))
207
185
  }
208
186
  });
@@ -234,11 +212,11 @@ class AgentLoopController {
234
212
  rejectionReason: "",
235
213
  changeRequest: "",
236
214
  metadata: {
237
- ...asObject(run.metadata),
238
- ...asObject(options.metadata),
239
- harnessTag: options.harnessTag || asObject(run.metadata).harnessTag || "default",
240
- harnessProfileId: options.harnessProfileId || asObject(run.metadata).harnessProfileId,
241
- harnessSettings: asObject(options.harnessSettings || asObject(run.metadata).harnessSettings),
215
+ ...(0, import_ctx_utils.asObject)(run.metadata),
216
+ ...(0, import_ctx_utils.asObject)(options.metadata),
217
+ harnessTag: options.harnessTag || (0, import_ctx_utils.asObject)(run.metadata).harnessTag || "default",
218
+ harnessProfileId: options.harnessProfileId || (0, import_ctx_utils.asObject)(run.metadata).harnessProfileId,
219
+ harnessSettings: (0, import_ctx_utils.asObject)(options.harnessSettings || (0, import_ctx_utils.asObject)(run.metadata).harnessSettings),
242
220
  approvalMode: "plan_first"
243
221
  },
244
222
  updatedAt: now()
@@ -252,7 +230,7 @@ class AgentLoopController {
252
230
  userId: options.userId,
253
231
  payload: {
254
232
  planVersion: nextPlanVersion,
255
- steps: plan.map((step, index) => normalizePlanKey(step, index))
233
+ steps: plan.map((step, index) => (0, import_ctx_utils.normalizePlanKey)(step, index))
256
234
  }
257
235
  });
258
236
  return this.getRunDetail(run.id);
@@ -377,20 +355,20 @@ class AgentLoopController {
377
355
  const created = await this.repository.createStep({
378
356
  runId: run.id,
379
357
  parentStepId: step.parentStepId,
380
- planKey: normalizePlanKey(step, i),
358
+ planKey: (0, import_ctx_utils.normalizePlanKey)(step, i),
381
359
  index: indexStart + i,
382
360
  title: step.title || `Step ${indexStart + i + 1}`,
383
361
  description: step.description || "",
384
- type: normalizeStepType(step.type),
362
+ type: (0, import_ctx_utils.normalizeStepType)(step.type),
385
363
  target: step.target || "",
386
364
  input: step.input || {},
387
365
  output: {},
388
366
  status: "pending",
389
367
  attempt: 0,
390
368
  maxAttempts: step.maxAttempts || policy.maxStepAttempts,
391
- dependsOn: asArray(step.dependsOn).map(String),
369
+ dependsOn: (0, import_ctx_utils.asArray)(step.dependsOn).map(String),
392
370
  dependencyPolicy: step.dependencyPolicy || ((_a = step.metadata) == null ? void 0 : _a.dependencyPolicy) || "require_success",
393
- metadata: asObject(step.metadata)
371
+ metadata: (0, import_ctx_utils.asObject)(step.metadata)
394
372
  });
395
373
  createdSteps.push(created);
396
374
  }
@@ -460,7 +438,6 @@ class AgentLoopController {
460
438
  });
461
439
  await this.repository.updateRun(run.id, {
462
440
  status: "running",
463
- currentStepId: step.id,
464
441
  updatedAt: now()
465
442
  });
466
443
  await this.repository.createEvent({
@@ -498,22 +475,18 @@ class AgentLoopController {
498
475
  if (step.status !== "running") {
499
476
  throw new Error(`Step ${step.id} cannot complete from status "${step.status}".`);
500
477
  }
501
- if (!run.currentStepId || String(run.currentStepId) !== String(step.id)) {
502
- throw new Error(`Step ${step.id} is not the current running step for run ${run.id}.`);
503
- }
504
478
  await this.repository.updateStep(step.id, {
505
479
  status: "succeeded",
506
480
  output: output === void 0 ? {} : output,
507
481
  error: "",
508
482
  skillExecutionId: options.skillExecutionId || step.skillExecutionId,
509
483
  agentExecutionSpanId: options.agentExecutionSpanId || step.agentExecutionSpanId,
510
- metadata: { ...asObject(step.metadata), ...asObject(options.metadata) },
484
+ metadata: { ...(0, import_ctx_utils.asObject)(step.metadata), ...(0, import_ctx_utils.asObject)(options.metadata) },
511
485
  endedAt: now(),
512
486
  updatedAt: now()
513
487
  });
514
488
  await this.repository.updateRun(run.id, {
515
489
  status: "running",
516
- currentStepId: null,
517
490
  updatedAt: now()
518
491
  });
519
492
  await this.repository.createEvent({
@@ -521,7 +494,7 @@ class AgentLoopController {
521
494
  stepId: step.id,
522
495
  type: "step_succeeded",
523
496
  title: `Completed: ${step.title || step.planKey}`,
524
- content: trimText(output, 2e3),
497
+ content: (0, import_ctx_utils.trimText)(output, 2e3),
525
498
  status: "succeeded",
526
499
  userId: options.userId
527
500
  });
@@ -536,20 +509,16 @@ class AgentLoopController {
536
509
  if (step.status !== "running") {
537
510
  throw new Error(`Step ${step.id} cannot fail from status "${step.status}".`);
538
511
  }
539
- if (!run.currentStepId || String(run.currentStepId) !== String(step.id)) {
540
- throw new Error(`Step ${step.id} is not the current running step for run ${run.id}.`);
541
- }
542
512
  const policy = normalizePolicy(run.policy);
543
513
  await this.repository.updateStep(step.id, {
544
514
  status: "failed",
545
- error: trimText(error, 1e4),
546
- metadata: { ...asObject(step.metadata), ...asObject(options.metadata) },
515
+ error: (0, import_ctx_utils.trimText)(error, 1e4),
516
+ metadata: { ...(0, import_ctx_utils.asObject)(step.metadata), ...(0, import_ctx_utils.asObject)(options.metadata) },
547
517
  endedAt: now(),
548
518
  updatedAt: now()
549
519
  });
550
520
  await this.repository.updateRun(run.id, {
551
521
  status: "running",
552
- currentStepId: null,
553
522
  updatedAt: now()
554
523
  });
555
524
  await this.repository.createEvent({
@@ -575,11 +544,6 @@ class AgentLoopController {
575
544
  if (!["pending", "running", "failed"].includes(step.status)) {
576
545
  throw new Error(`Step ${step.id} cannot skip from status "${step.status}".`);
577
546
  }
578
- if (step.status === "running") {
579
- if (!run.currentStepId || String(run.currentStepId) !== String(step.id)) {
580
- throw new Error(`Step ${step.id} is not the current running step for run ${run.id}.`);
581
- }
582
- }
583
547
  await this.repository.updateStep(step.id, {
584
548
  status: "skipped",
585
549
  error: reason,
@@ -606,11 +570,6 @@ class AgentLoopController {
606
570
  if (!["pending", "running"].includes(step.status)) {
607
571
  throw new Error(`Step ${step.id} cannot request approval for status "${step.status}".`);
608
572
  }
609
- if (step.status === "running") {
610
- if (!run.currentStepId || String(run.currentStepId) !== String(step.id)) {
611
- throw new Error(`Step ${step.id} is not the current running step for run ${run.id}.`);
612
- }
613
- }
614
573
  await this.repository.updateStep(step.id, {
615
574
  status: "waiting_user",
616
575
  approval: approval || {},
@@ -646,9 +605,6 @@ class AgentLoopController {
646
605
  if (run.status !== "waiting_user" || step.status !== "waiting_user") {
647
606
  throw new Error("Run is not waiting for user approval.");
648
607
  }
649
- if (run.currentStepId && String(run.currentStepId) !== String(step.id)) {
650
- throw new Error(`Step ${step.id} is not the current waiting step for run ${run.id}.`);
651
- }
652
608
  if (options.approved) {
653
609
  const nextValues = {
654
610
  status: "pending",
@@ -708,12 +664,35 @@ class AgentLoopController {
708
664
  if (Number(step.attempt || 0) >= Number(step.maxAttempts || policy.maxStepAttempts)) {
709
665
  throw new Error(`Step ${step.id} reached maxAttempts=${step.maxAttempts}.`);
710
666
  }
711
- await this.repository.updateStep(step.id, {
667
+ let routedTarget;
668
+ let routeReason;
669
+ if (step.type === "sub_agent" && run.leaderUsername) {
670
+ const target = step.target || "";
671
+ if (target) {
672
+ const circuitBreaker = (0, import_CircuitBreaker.getCircuitBreaker)();
673
+ const state = circuitBreaker.getState(target);
674
+ const attempt = Number(step.attempt || 0);
675
+ const shouldRoute = (state == null ? void 0 : state.state) === "open" || state && state.failures >= 2 && attempt >= 1;
676
+ if (shouldRoute) {
677
+ const alternatives = await this.registryService.findAlternativeSubAgents(run.leaderUsername, target);
678
+ if (alternatives.length > 0) {
679
+ const chosen = alternatives[0];
680
+ routedTarget = chosen.username;
681
+ routeReason = `Sub-agent "${target}" has ${(state == null ? void 0 : state.failures) || 0} failure(s) (circuit: ${(state == null ? void 0 : state.state) || "closed"}). Routing retry to alternative "${routedTarget}".`;
682
+ }
683
+ }
684
+ }
685
+ }
686
+ const updateValues = {
712
687
  status: "pending",
713
688
  error: "",
714
689
  endedAt: null,
715
690
  updatedAt: now()
716
- });
691
+ };
692
+ if (routedTarget) {
693
+ updateValues.target = routedTarget;
694
+ }
695
+ await this.repository.updateStep(step.id, updateValues);
717
696
  await this.repository.updateRun(run.id, {
718
697
  status: "running",
719
698
  updatedAt: now()
@@ -722,9 +701,11 @@ class AgentLoopController {
722
701
  runId: run.id,
723
702
  stepId: step.id,
724
703
  type: "step_retry",
725
- title: `Retry queued: ${step.title || step.planKey}`,
704
+ title: routedTarget ? `Retry routed: ${step.title || step.planKey} \u2192 ${routedTarget}` : `Retry queued: ${step.title || step.planKey}`,
705
+ content: routeReason || "",
726
706
  status: "pending",
727
- userId: options.userId
707
+ userId: options.userId,
708
+ payload: routedTarget ? { originalTarget: step.target, routedTarget, reason: routeReason } : void 0
728
709
  });
729
710
  return this.getRunSnapshot(run.id);
730
711
  }
@@ -751,7 +732,7 @@ class AgentLoopController {
751
732
  finalAnswer: finalAnswer || "",
752
733
  summary: options.summary || run.summary,
753
734
  currentStepId: null,
754
- metadata: { ...asObject(run.metadata), evidence: options.evidence },
735
+ metadata: { ...(0, import_ctx_utils.asObject)(run.metadata), evidence: options.evidence },
755
736
  endedAt: now(),
756
737
  updatedAt: now()
757
738
  });
@@ -766,6 +747,32 @@ class AgentLoopController {
766
747
  });
767
748
  return this.getRunSnapshot(run.id);
768
749
  }
750
+ async stepFeedback(stepId, feedback, options = {}) {
751
+ const step = await this.repository.requireStep(stepId);
752
+ const run = await this.repository.requireRun(step.runId);
753
+ if (step.status !== "succeeded" && step.status !== "failed") {
754
+ throw new Error(`Cannot provide feedback for step ${step.id} in status "${step.status}".`);
755
+ }
756
+ await this.repository.createEvent({
757
+ runId: run.id,
758
+ stepId: step.id,
759
+ type: "step_feedback",
760
+ title: `Feedback: ${feedback.rating === "positive" ? "\u{1F44D}" : "\u{1F44E}"} ${step.title || step.planKey}`,
761
+ content: feedback.comment || "",
762
+ status: step.status,
763
+ userId: options.userId,
764
+ payload: {
765
+ rating: feedback.rating,
766
+ comment: feedback.comment || "",
767
+ category: feedback.category || "",
768
+ stepType: step.type,
769
+ stepPlanKey: step.planKey,
770
+ stepTarget: step.target || "",
771
+ attempt: step.attempt || 0
772
+ }
773
+ });
774
+ return this.getRunSnapshot(run.id);
775
+ }
769
776
  async cancelRun(runId, options = {}) {
770
777
  const run = await this.repository.requireRun(runId);
771
778
  if (TERMINAL_RUN_STATUSES.has(run.status)) {
@@ -808,7 +815,7 @@ class AgentLoopController {
808
815
  try {
809
816
  let iterations = 0;
810
817
  let snapshot = await this.getRunSnapshot(runId);
811
- const harnessSettings = asObject((_b = (_a = snapshot.run) == null ? void 0 : _a.metadata) == null ? void 0 : _b.harnessSettings);
818
+ const harnessSettings = (0, import_ctx_utils.asObject)((_b = (_a = snapshot.run) == null ? void 0 : _a.metadata) == null ? void 0 : _b.harnessSettings);
812
819
  const maxControllerSteps = Math.max(
813
820
  1,
814
821
  Math.min(
@@ -817,39 +824,67 @@ class AgentLoopController {
817
824
  )
818
825
  );
819
826
  const policy = normalizePolicy(snapshot.run.policy);
820
- while (snapshot.nextStep && iterations < maxControllerSteps) {
821
- iterations += 1;
822
- const nextStep = snapshot.nextStep;
823
- snapshot = await this.startStep(nextStep.id, { userId: options.userId });
824
- const runningStep = snapshot.steps.find((step) => String(step.id) === String(nextStep.id)) || nextStep;
825
- try {
826
- const output = await this.harness.executeStep(snapshot.run, runningStep, options);
827
- snapshot = await this.completeStep(runningStep.id, output, {
828
- userId: options.userId,
829
- metadata: { controller: "agent-loop-service" }
830
- });
831
- } catch (error) {
832
- if ((error == null ? void 0 : error.message) === "requires_approval") {
833
- snapshot = await this.requestApproval(runningStep.id, {
834
- prompt: `Execution of step "${runningStep.title}" requires permission.`
835
- }, {
836
- userId: options.userId,
837
- reason: "Dynamic tool approval required by policy."
827
+ const maxConcurrency = policy.maxConcurrency;
828
+ while (snapshot.nextSteps && snapshot.nextSteps.length > 0 && iterations < maxControllerSteps) {
829
+ const batch = snapshot.nextSteps.slice(0, maxConcurrency);
830
+ iterations += batch.length;
831
+ if (this.tokenTracker) {
832
+ const budgetCheck = await this.tokenTracker.checkBudget(runId);
833
+ if (!budgetCheck.allowed) {
834
+ return this.finishRun(runId, budgetCheck.reason, {
835
+ status: "failed",
836
+ summary: budgetCheck.reason,
837
+ userId: options.userId
838
838
  });
839
- break;
840
839
  }
841
- snapshot = await this.failStep(runningStep.id, (error == null ? void 0 : error.message) || String(error), {
842
- userId: options.userId,
843
- metadata: { controller: "agent-loop-service" }
844
- });
845
- const failedStep = snapshot.steps.find((step) => String(step.id) === String(runningStep.id));
846
- if (!failedStep || Number(failedStep.attempt || 0) >= Number(failedStep.maxAttempts || policy.maxStepAttempts)) {
847
- break;
840
+ }
841
+ await Promise.all(batch.map((step) => this.startStep(step.id, { userId: options.userId })));
842
+ snapshot = await this.getRunSnapshot(runId);
843
+ const runningSteps = batch.map(
844
+ (step) => snapshot.steps.find((s) => String(s.id) === String(step.id)) || step
845
+ );
846
+ const results = await Promise.allSettled(
847
+ runningSteps.map((step) => this.harness.executeStep(snapshot.run, step, options))
848
+ );
849
+ for (let i = 0; i < results.length; i++) {
850
+ const runningStep = runningSteps[i];
851
+ const result = results[i];
852
+ if (result.status === "fulfilled") {
853
+ snapshot = await this.completeStep(runningStep.id, result.value, {
854
+ userId: options.userId,
855
+ metadata: { controller: "agent-loop-service" }
856
+ });
857
+ } else {
858
+ const error = result.reason;
859
+ if ((error == null ? void 0 : error.message) === "requires_approval") {
860
+ snapshot = await this.requestApproval(
861
+ runningStep.id,
862
+ { prompt: `Execution of step "${runningStep.title}" requires permission.` },
863
+ { userId: options.userId, reason: "Dynamic tool approval required by policy." }
864
+ );
865
+ break;
866
+ }
867
+ snapshot = await this.failStep(runningStep.id, (error == null ? void 0 : error.message) || String(error), {
868
+ userId: options.userId,
869
+ metadata: { controller: "agent-loop-service" }
870
+ });
871
+ const failedStep = snapshot.steps.find((s) => String(s.id) === String(runningStep.id));
872
+ if (failedStep && Number(failedStep.attempt || 0) < Number(failedStep.maxAttempts || policy.maxStepAttempts)) {
873
+ const attempt = Number(failedStep.attempt || 1);
874
+ const baseDelay = 1e3;
875
+ const maxDelay = 6e4;
876
+ const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
877
+ await new Promise((resolve) => setTimeout(resolve, delay));
878
+ }
848
879
  }
849
880
  }
881
+ snapshot = await this.getRunSnapshot(runId);
882
+ if (snapshot.run.status === "waiting_user") {
883
+ break;
884
+ }
850
885
  }
851
886
  snapshot = await this.getRunSnapshot(runId);
852
- if (iterations >= maxControllerSteps && snapshot.nextStep) {
887
+ if (iterations >= maxControllerSteps && snapshot.nextSteps && snapshot.nextSteps.length > 0) {
853
888
  return this.finishRun(runId, `Agent loop stopped after ${maxControllerSteps} controller steps.`, {
854
889
  status: "failed",
855
890
  summary: "Controller iteration limit reached.",
@@ -861,7 +896,9 @@ class AgentLoopController {
861
896
  }
862
897
  const steps = snapshot.steps || [];
863
898
  const failed = steps.filter((step) => step.status === "failed");
864
- const unfinished = steps.filter((step) => !TERMINAL_STEP_STATUSES.has(step.status) && step.status !== "failed");
899
+ const unfinished = steps.filter(
900
+ (step) => !TERMINAL_STEP_STATUSES.has(step.status) && step.status !== "failed"
901
+ );
865
902
  if (failed.length || unfinished.length) {
866
903
  return this.finishRun(
867
904
  runId,
@@ -869,7 +906,10 @@ class AgentLoopController {
869
906
  {
870
907
  status: "failed",
871
908
  summary: ((_c = failed[0]) == null ? void 0 : _c.error) || "No executable step is available.",
872
- evidence: { failedStepIds: failed.map((step) => step.id), unfinishedStepIds: unfinished.map((step) => step.id) },
909
+ evidence: {
910
+ failedStepIds: failed.map((step) => step.id),
911
+ unfinishedStepIds: unfinished.map((step) => step.id)
912
+ },
873
913
  userId: options.userId
874
914
  }
875
915
  );
@@ -891,11 +931,12 @@ class AgentLoopController {
891
931
  async getRunSnapshot(runId) {
892
932
  const run = await this.repository.requireRun(runId);
893
933
  const steps = await this.repository.getSteps(run.id);
894
- const nextStep = this.pickNextStep(steps, run.policy);
934
+ const nextSteps = this.pickNextSteps(steps, run.policy);
935
+ const runningStepIds = steps.filter((s) => s.status === "running").map((s) => s.id);
895
936
  return {
896
- run,
937
+ run: { ...run, runningStepIds },
897
938
  steps,
898
- nextStep
939
+ nextSteps
899
940
  };
900
941
  }
901
942
  async getRunDetail(runId) {
@@ -910,28 +951,26 @@ class AgentLoopController {
910
951
  skillExecutions
911
952
  };
912
953
  }
913
- pickNextStep(steps, runPolicy) {
954
+ pickNextSteps(steps, runPolicy) {
914
955
  var _a;
915
956
  const byPlanKey = new Map(steps.map((step) => [String(step.planKey), step]));
916
957
  const policy = normalizePolicy(runPolicy);
917
958
  const candidates = steps.filter(
918
959
  (step) => step.status === "pending" || step.status === "failed" && Number(step.attempt || 0) < Number(step.maxAttempts || policy.maxStepAttempts)
919
960
  ).sort((a, b) => Number(a.index || 0) - Number(b.index || 0));
961
+ const ready = [];
920
962
  for (const step of candidates) {
921
- const dependencies = asArray(step.dependsOn).map(String);
963
+ const dependencies = (0, import_ctx_utils.asArray)(step.dependsOn).map(String);
922
964
  const allowSkipped = step.dependencyPolicy === "allow_skipped" || ((_a = step.metadata) == null ? void 0 : _a.dependencyPolicy) === "allow_skipped";
923
- const ready = dependencies.every((key) => {
965
+ const allDepsReady = dependencies.every((key) => {
924
966
  const dependency = byPlanKey.get(key);
925
967
  return (dependency == null ? void 0 : dependency.status) === "succeeded" || allowSkipped && (dependency == null ? void 0 : dependency.status) === "skipped";
926
968
  });
927
- if (ready) {
928
- return {
929
- ...step,
930
- retryable: step.status === "failed"
931
- };
969
+ if (allDepsReady) {
970
+ ready.push(step);
932
971
  }
933
972
  }
934
- return null;
973
+ return ready;
935
974
  }
936
975
  }
937
976
  // Annotate the CommonJS export names for ESM import in node:
@@ -29,24 +29,8 @@ __export(AgentLoopRepository_exports, {
29
29
  AgentLoopRepository: () => AgentLoopRepository
30
30
  });
31
31
  module.exports = __toCommonJS(AgentLoopRepository_exports);
32
- function toPlain(record) {
33
- var _a;
34
- return ((_a = record == null ? void 0 : record.toJSON) == null ? void 0 : _a.call(record)) || record;
35
- }
36
- function trimText(value, max = 5e4) {
37
- let text = "";
38
- if (typeof value === "string") {
39
- text = value;
40
- } else if (value != null) {
41
- try {
42
- text = JSON.stringify(value);
43
- } catch {
44
- text = String(value);
45
- }
46
- }
47
- return text.length > max ? `${text.slice(0, max)}
48
- ...[truncated]` : text;
49
- }
32
+ var import_ctx_utils = require("../utils/ctx-utils");
33
+ var import_RunEventBus = require("./RunEventBus");
50
34
  class AgentLoopRepository {
51
35
  constructor(plugin) {
52
36
  this.plugin = plugin;
@@ -58,7 +42,7 @@ class AgentLoopRepository {
58
42
  const run = await this.db.getRepository("agentLoopRuns").findOne({
59
43
  filter: { id: runId }
60
44
  });
61
- return run ? toPlain(run) : null;
45
+ return run ? (0, import_ctx_utils.toPlain)(run) : null;
62
46
  }
63
47
  async requireRun(runId) {
64
48
  const run = await this.getRun(runId);
@@ -71,7 +55,7 @@ class AgentLoopRepository {
71
55
  const run = await this.db.getRepository("agentLoopRuns").create({
72
56
  values
73
57
  });
74
- return toPlain(run);
58
+ return (0, import_ctx_utils.toPlain)(run);
75
59
  }
76
60
  async updateRun(runId, values) {
77
61
  await this.db.getRepository("agentLoopRuns").update({
@@ -83,7 +67,7 @@ class AgentLoopRepository {
83
67
  const step = await this.db.getRepository("agentLoopSteps").findOne({
84
68
  filter: { id: stepId }
85
69
  });
86
- return step ? toPlain(step) : null;
70
+ return step ? (0, import_ctx_utils.toPlain)(step) : null;
87
71
  }
88
72
  async requireStep(stepId) {
89
73
  const step = await this.getStep(stepId);
@@ -96,7 +80,7 @@ class AgentLoopRepository {
96
80
  const step = await this.db.getRepository("agentLoopSteps").create({
97
81
  values
98
82
  });
99
- return toPlain(step);
83
+ return (0, import_ctx_utils.toPlain)(step);
100
84
  }
101
85
  async updateStep(stepId, values) {
102
86
  await this.db.getRepository("agentLoopSteps").update({
@@ -110,18 +94,20 @@ class AgentLoopRepository {
110
94
  sort: ["index", "createdAt"],
111
95
  pageSize: 1e3
112
96
  });
113
- return steps.map(toPlain);
97
+ return steps.map(import_ctx_utils.toPlain);
114
98
  }
115
99
  async createEvent(values) {
116
100
  const record = await this.db.getRepository("agentLoopEvents").create({
117
101
  values: {
118
102
  ...values,
119
- content: trimText(values.content || "", 1e4),
103
+ content: (0, import_ctx_utils.trimText)(values.content || "", 1e4),
120
104
  payload: values.payload || {},
121
105
  createdAt: /* @__PURE__ */ new Date()
122
106
  }
123
107
  });
124
- return toPlain(record);
108
+ const event = (0, import_ctx_utils.toPlain)(record);
109
+ (0, import_RunEventBus.getRunEventBus)().emit(values.runId, event);
110
+ return event;
125
111
  }
126
112
  async getEvents(runId) {
127
113
  const events = await this.db.getRepository("agentLoopEvents").find({
@@ -129,7 +115,7 @@ class AgentLoopRepository {
129
115
  sort: ["createdAt"],
130
116
  pageSize: 500
131
117
  });
132
- return events.map(toPlain);
118
+ return events.map(import_ctx_utils.toPlain);
133
119
  }
134
120
  async getLinkedSpans(runId, rootRunId) {
135
121
  const repo = this.db.getRepository("agentExecutionSpans");
@@ -143,7 +129,7 @@ class AgentLoopRepository {
143
129
  sort: ["createdAt"],
144
130
  pageSize: 1e3
145
131
  });
146
- return rows.map(toPlain);
132
+ return rows.map(import_ctx_utils.toPlain);
147
133
  } catch {
148
134
  if (!rootRunId) return [];
149
135
  const rows = await repo.find({
@@ -151,7 +137,7 @@ class AgentLoopRepository {
151
137
  sort: ["createdAt"],
152
138
  pageSize: 1e3
153
139
  });
154
- return rows.map(toPlain);
140
+ return rows.map(import_ctx_utils.toPlain);
155
141
  }
156
142
  }
157
143
  async getLinkedSkillExecutions(runId, steps) {
@@ -171,7 +157,7 @@ class AgentLoopRepository {
171
157
  sort: ["createdAt"],
172
158
  pageSize: 1e3
173
159
  });
174
- return rows.map(toPlain);
160
+ return rows.map(import_ctx_utils.toPlain);
175
161
  }
176
162
  async lockRun(runId, lockName, durationMs) {
177
163
  const repo = this.db.getRepository("agentLoopRuns");
@@ -186,11 +172,7 @@ class AgentLoopRepository {
186
172
  {
187
173
  where: {
188
174
  id: runId,
189
- [Op.or]: [
190
- { lockedBy: null },
191
- { lockedUntil: { [Op.lt]: now.toISOString() } },
192
- { lockedBy: lockName }
193
- ]
175
+ [Op.or]: [{ lockedBy: null }, { lockedUntil: { [Op.lt]: now.toISOString() } }, { lockedBy: lockName }]
194
176
  }
195
177
  }
196
178
  );