@opengoat/core 2026.2.15 → 2026.2.17

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 (87) hide show
  1. package/dist/core/agents/application/agent.service.d.ts +12 -3
  2. package/dist/core/agents/application/agent.service.js +98 -48
  3. package/dist/core/agents/application/agent.service.js.map +1 -1
  4. package/dist/core/boards/application/board.service.d.ts +2 -0
  5. package/dist/core/boards/application/board.service.js +36 -2
  6. package/dist/core/boards/application/board.service.js.map +1 -1
  7. package/dist/core/bootstrap/application/bootstrap.service.js +4 -1
  8. package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -1
  9. package/dist/core/opengoat/application/opengoat.service.d.ts +5 -3
  10. package/dist/core/opengoat/application/opengoat.service.helpers.d.ts +12 -2
  11. package/dist/core/opengoat/application/opengoat.service.helpers.js +86 -8
  12. package/dist/core/opengoat/application/opengoat.service.helpers.js.map +1 -1
  13. package/dist/core/opengoat/application/opengoat.service.js +238 -89
  14. package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
  15. package/dist/core/orchestration/application/orchestration.service.d.ts +1 -0
  16. package/dist/core/orchestration/application/orchestration.service.js +42 -29
  17. package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
  18. package/dist/core/providers/application/provider.service.d.ts +34 -4
  19. package/dist/core/providers/application/provider.service.js +799 -82
  20. package/dist/core/providers/application/provider.service.js.map +1 -1
  21. package/dist/core/providers/index.d.ts +1 -1
  22. package/dist/core/providers/index.js.map +1 -1
  23. package/dist/core/providers/loader.js +4 -2
  24. package/dist/core/providers/loader.js.map +1 -1
  25. package/dist/core/providers/openclaw-gateway-rpc.d.ts +20 -0
  26. package/dist/core/providers/openclaw-gateway-rpc.js +629 -0
  27. package/dist/core/providers/openclaw-gateway-rpc.js.map +1 -0
  28. package/dist/core/providers/provider-module.d.ts +11 -0
  29. package/dist/core/providers/provider-module.js +8 -1
  30. package/dist/core/providers/provider-module.js.map +1 -1
  31. package/dist/core/providers/providers/claude-code/index.d.ts +4 -0
  32. package/dist/core/providers/providers/claude-code/index.js +32 -0
  33. package/dist/core/providers/providers/claude-code/index.js.map +1 -0
  34. package/dist/core/providers/providers/claude-code/provider.d.ts +13 -0
  35. package/dist/core/providers/providers/claude-code/provider.js +152 -0
  36. package/dist/core/providers/providers/claude-code/provider.js.map +1 -0
  37. package/dist/core/providers/providers/codex/index.d.ts +4 -0
  38. package/dist/core/providers/providers/codex/index.js +32 -0
  39. package/dist/core/providers/providers/codex/index.js.map +1 -0
  40. package/dist/core/providers/providers/codex/provider.d.ts +12 -0
  41. package/dist/core/providers/providers/codex/provider.js +156 -0
  42. package/dist/core/providers/providers/codex/provider.js.map +1 -0
  43. package/dist/core/providers/providers/cursor/index.d.ts +4 -0
  44. package/dist/core/providers/providers/cursor/index.js +32 -0
  45. package/dist/core/providers/providers/cursor/index.js.map +1 -0
  46. package/dist/core/providers/providers/cursor/provider.d.ts +12 -0
  47. package/dist/core/providers/providers/cursor/provider.js +161 -0
  48. package/dist/core/providers/providers/cursor/provider.js.map +1 -0
  49. package/dist/core/providers/providers/gemini-cli/index.d.ts +4 -0
  50. package/dist/core/providers/providers/gemini-cli/index.js +36 -0
  51. package/dist/core/providers/providers/gemini-cli/index.js.map +1 -0
  52. package/dist/core/providers/providers/gemini-cli/provider.d.ts +11 -0
  53. package/dist/core/providers/providers/gemini-cli/provider.js +122 -0
  54. package/dist/core/providers/providers/gemini-cli/provider.js.map +1 -0
  55. package/dist/core/providers/providers/openclaw/index.js +8 -0
  56. package/dist/core/providers/providers/openclaw/index.js.map +1 -1
  57. package/dist/core/providers/providers/openclaw/provider.js +1 -0
  58. package/dist/core/providers/providers/openclaw/provider.js.map +1 -1
  59. package/dist/core/providers/providers/opencode/index.d.ts +4 -0
  60. package/dist/core/providers/providers/opencode/index.js +27 -0
  61. package/dist/core/providers/providers/opencode/index.js.map +1 -0
  62. package/dist/core/providers/providers/opencode/provider.d.ts +12 -0
  63. package/dist/core/providers/providers/opencode/provider.js +130 -0
  64. package/dist/core/providers/providers/opencode/provider.js.map +1 -0
  65. package/dist/core/providers/providers/registry.d.ts +2 -0
  66. package/dist/core/providers/providers/registry.js +17 -0
  67. package/dist/core/providers/providers/registry.js.map +1 -0
  68. package/dist/core/providers/registry.d.ts +2 -1
  69. package/dist/core/providers/registry.js +40 -0
  70. package/dist/core/providers/registry.js.map +1 -1
  71. package/dist/core/providers/types.d.ts +1 -0
  72. package/dist/core/scenarios/application/scenario-runner.service.js +2 -1
  73. package/dist/core/scenarios/application/scenario-runner.service.js.map +1 -1
  74. package/dist/core/sessions/application/session.service.d.ts +0 -2
  75. package/dist/core/sessions/application/session.service.js +10 -73
  76. package/dist/core/sessions/application/session.service.js.map +1 -1
  77. package/dist/core/sessions/domain/session.d.ts +0 -3
  78. package/dist/core/sessions/domain/session.js.map +1 -1
  79. package/dist/core/skills/application/skill.service.js +4 -1
  80. package/dist/core/skills/application/skill.service.js.map +1 -1
  81. package/dist/core/templates/assets/ceo/BOOTSTRAP.md +3 -3
  82. package/dist/core/templates/assets/ceo/ROLE.md +3 -2
  83. package/dist/core/templates/assets/skills/og-boards/SKILL.md +65 -0
  84. package/dist/core/templates/default-templates.d.ts +1 -2
  85. package/dist/core/templates/default-templates.js +7 -7
  86. package/dist/core/templates/default-templates.js.map +1 -1
  87. package/package.json +3 -2
@@ -2,7 +2,7 @@ import { AgentManifestService } from "../../agents/application/agent-manifest.se
2
2
  import { AgentService } from "../../agents/application/agent.service.js";
3
3
  import { BoardService, } from "../../boards/index.js";
4
4
  import { BootstrapService } from "../../bootstrap/application/bootstrap.service.js";
5
- import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../domain/agent-id.js";
5
+ import { DEFAULT_AGENT_ID, isDefaultAgentId, normalizeAgentId, } from "../../domain/agent-id.js";
6
6
  import { createNoopLogger } from "../../logging/index.js";
7
7
  import { OrchestrationService, } from "../../orchestration/index.js";
8
8
  import { ProviderService } from "../../providers/application/provider.service.js";
@@ -10,7 +10,7 @@ import { ProviderCommandNotFoundError, createDefaultProviderRegistry, } from "..
10
10
  import { dirname, resolve as resolvePath } from "node:path";
11
11
  import { SessionService, } from "../../sessions/index.js";
12
12
  import { SkillService, } from "../../skills/index.js";
13
- import { assertAgentExists, buildBlockedTaskMessage, buildInactiveAgentMessage, buildInactiveSessionRef, buildReporteeStats, buildTaskSessionRef, buildTodoTaskMessage, collectAllReportees, containsAgentNotFoundMessage, containsAlreadyExistsMessage, extractManagedSkillsDir, extractOpenClawAgents, isSpawnPermissionOrMissing, pathIsWithin, pathMatches, prepareOpenClawCommandEnv, resolveInactiveAgentNotificationTarget, resolveInactiveMinutes, toErrorMessage, } from "./opengoat.service.helpers.js";
13
+ import { assertAgentExists, buildBlockedTaskMessage, buildInactiveAgentMessage, buildNotificationSessionRef, buildPendingTaskMessage, buildReporteeStats, buildTodoTaskMessage, collectAllReportees, containsAgentNotFoundMessage, containsAlreadyExistsMessage, extractManagedSkillsDir, extractOpenClawAgents, isSpawnPermissionOrMissing, pathIsWithin, pathMatches, prepareOpenClawCommandEnv, resolveInactiveAgentNotificationTarget, resolveInactiveMinutes, resolveMaxParallelFlows, toErrorMessage, } from "./opengoat.service.helpers.js";
14
14
  const OPENCLAW_PROVIDER_ID = "openclaw";
15
15
  const OPENCLAW_DEFAULT_AGENT_ID = "main";
16
16
  const OPENCLAW_AGENT_SANDBOX_MODE = "off";
@@ -255,6 +255,10 @@ export class OpenGoatService {
255
255
  }
256
256
  async createAgent(rawName, options = {}) {
257
257
  const identity = this.agentService.normalizeAgentName(rawName);
258
+ const managerAgentId = resolveCreateAgentManagerId(identity.id, options.reportsTo);
259
+ if (managerAgentId) {
260
+ await this.assertManagerSupportsReportees(managerAgentId);
261
+ }
258
262
  const paths = this.pathsProvider.getPaths();
259
263
  const created = await this.agentService.ensureAgent(paths, identity, {
260
264
  type: options.type,
@@ -308,6 +312,8 @@ export class OpenGoatService {
308
312
  displayName: created.agent.displayName,
309
313
  role: options.role?.trim() ??
310
314
  (created.alreadyExisted ? created.agent.role : ""),
315
+ }, {
316
+ syncBootstrapMarkdown: false,
311
317
  });
312
318
  created.createdPaths.push(...workspaceBootstrap.createdPaths);
313
319
  created.skippedPaths.push(...workspaceBootstrap.skippedPaths);
@@ -460,7 +466,9 @@ export class OpenGoatService {
460
466
  }
461
467
  async setAgentProvider(agentId, providerId) {
462
468
  const paths = this.pathsProvider.getPaths();
463
- return this.providerService.setAgentProvider(paths, agentId, providerId);
469
+ const binding = await this.providerService.setAgentProvider(paths, agentId, providerId);
470
+ await this.ensureAgentProviderRoleSkills(paths, binding.agentId);
471
+ return binding;
464
472
  }
465
473
  async getOpenClawGatewayConfig() {
466
474
  const paths = this.pathsProvider.getPaths();
@@ -518,6 +526,22 @@ export class OpenGoatService {
518
526
  const paths = this.pathsProvider.getPaths();
519
527
  return this.boardService.addTaskWorklog(paths, actorId, taskId, content);
520
528
  }
529
+ async assertManagerSupportsReportees(managerAgentId) {
530
+ const paths = this.pathsProvider.getPaths();
531
+ const agents = await this.agentService.listAgents(paths);
532
+ if (!agents.some((agent) => agent.id === managerAgentId)) {
533
+ return;
534
+ }
535
+ const managerBinding = await this.providerService.getAgentProvider(paths, managerAgentId);
536
+ const providers = await this.providerService.listProviders();
537
+ const provider = providers.find((candidate) => candidate.id === managerBinding.providerId);
538
+ if (!provider) {
539
+ throw new Error(`Provider "${managerBinding.providerId}" is not available for manager "${managerAgentId}".`);
540
+ }
541
+ if (!provider.capabilities.reportees) {
542
+ throw new Error(`Cannot assign "${managerAgentId}" as manager because provider "${provider.displayName}" does not support reportees.`);
543
+ }
544
+ }
521
545
  async runTaskCronCycle(options = {}) {
522
546
  const paths = this.pathsProvider.getPaths();
523
547
  const ranAt = this.resolveNowIso();
@@ -525,15 +549,14 @@ export class OpenGoatService {
525
549
  const inactiveMinutes = resolveInactiveMinutes(options.inactiveMinutes);
526
550
  const notificationTarget = resolveInactiveAgentNotificationTarget(options.notificationTarget);
527
551
  const notifyInactiveAgents = options.notifyInactiveAgents ?? true;
552
+ const maxParallelFlows = resolveMaxParallelFlows(options.maxParallelFlows);
528
553
  const inactiveCandidates = notifyInactiveAgents
529
554
  ? await this.collectInactiveAgents(paths, manifests, inactiveMinutes, notificationTarget)
530
555
  : [];
531
- const latestCeoProjectPath = notifyInactiveAgents
532
- ? await this.resolveLatestProjectPathForAgent(paths, DEFAULT_AGENT_ID)
533
- : undefined;
534
556
  const tasks = await this.boardService.listTasks(paths, { limit: 10_000 });
535
- const taskStatusDispatch = await this.dispatchTaskStatusAutomations(paths, tasks, manifests);
536
- const inactiveDispatches = await this.dispatchInactiveAgentAutomations(paths, inactiveCandidates, inactiveMinutes, latestCeoProjectPath);
557
+ const pendingTaskIds = new Set(await this.boardService.listPendingTaskIdsOlderThan(paths, inactiveMinutes));
558
+ const taskStatusDispatch = await this.dispatchTaskStatusAutomations(paths, tasks, manifests, pendingTaskIds, inactiveMinutes, ranAt, maxParallelFlows);
559
+ const inactiveDispatches = await this.dispatchInactiveAgentAutomations(paths, inactiveCandidates, inactiveMinutes, ranAt, maxParallelFlows);
537
560
  const dispatches = [
538
561
  ...taskStatusDispatch.dispatches,
539
562
  ...inactiveDispatches,
@@ -550,57 +573,92 @@ export class OpenGoatService {
550
573
  dispatches,
551
574
  };
552
575
  }
553
- async dispatchTaskStatusAutomations(paths, tasks, manifests) {
576
+ async dispatchTaskStatusAutomations(paths, tasks, manifests, pendingTaskIds, pendingMinutes, notificationTimestamp, maxParallelFlows) {
554
577
  const manifestsById = new Map(manifests.map((manifest) => [manifest.agentId, manifest]));
555
- const dispatches = [];
578
+ const requests = [];
556
579
  let todoTasks = 0;
580
+ let pendingTasks = 0;
557
581
  let blockedTasks = 0;
558
582
  for (const task of tasks) {
559
- if (task.status !== "todo" && task.status !== "blocked") {
560
- continue;
561
- }
562
583
  if (task.status === "todo") {
563
584
  todoTasks += 1;
564
585
  const targetAgentId = task.assignedTo;
565
- const sessionRef = buildTaskSessionRef(targetAgentId, task.taskId);
566
- const message = buildTodoTaskMessage({ task });
567
- const result = await this.dispatchAutomationMessage(paths, targetAgentId, sessionRef, message);
568
- dispatches.push({
586
+ const sessionRef = buildNotificationSessionRef(targetAgentId);
587
+ const message = buildTodoTaskMessage({
588
+ task,
589
+ notificationTimestamp,
590
+ });
591
+ requests.push({
569
592
  kind: "todo",
570
593
  targetAgentId,
571
594
  sessionRef,
572
595
  taskId: task.taskId,
573
- ok: result.ok,
574
- error: result.error,
596
+ message,
575
597
  });
576
598
  continue;
577
599
  }
578
- blockedTasks += 1;
579
- const assigneeManifest = manifestsById.get(task.assignedTo);
580
- const managerAgentId = normalizeAgentId(assigneeManifest?.metadata.reportsTo ?? "") ||
581
- DEFAULT_AGENT_ID;
582
- const sessionRef = buildTaskSessionRef(managerAgentId, task.taskId);
583
- const message = buildBlockedTaskMessage({ task });
584
- const result = await this.dispatchAutomationMessage(paths, managerAgentId, sessionRef, message);
585
- dispatches.push({
586
- kind: "blocked",
587
- targetAgentId: managerAgentId,
588
- sessionRef,
589
- taskId: task.taskId,
600
+ if (task.status === "pending") {
601
+ if (!pendingTaskIds.has(task.taskId)) {
602
+ continue;
603
+ }
604
+ pendingTasks += 1;
605
+ const targetAgentId = task.assignedTo;
606
+ const sessionRef = buildNotificationSessionRef(targetAgentId);
607
+ const message = buildPendingTaskMessage({
608
+ task,
609
+ pendingMinutes,
610
+ notificationTimestamp,
611
+ });
612
+ requests.push({
613
+ kind: "pending",
614
+ targetAgentId,
615
+ sessionRef,
616
+ taskId: task.taskId,
617
+ message,
618
+ });
619
+ continue;
620
+ }
621
+ if (task.status === "blocked") {
622
+ blockedTasks += 1;
623
+ const assigneeManifest = manifestsById.get(task.assignedTo);
624
+ const managerAgentId = normalizeAgentId(assigneeManifest?.metadata.reportsTo ?? "") ||
625
+ DEFAULT_AGENT_ID;
626
+ const sessionRef = buildNotificationSessionRef(managerAgentId);
627
+ const message = buildBlockedTaskMessage({
628
+ task,
629
+ notificationTimestamp,
630
+ });
631
+ requests.push({
632
+ kind: "blocked",
633
+ targetAgentId: managerAgentId,
634
+ sessionRef,
635
+ taskId: task.taskId,
636
+ message,
637
+ });
638
+ }
639
+ }
640
+ const dispatches = await runWithConcurrencyByKey(requests, maxParallelFlows, (request) => normalizeAgentId(request.targetAgentId) || DEFAULT_AGENT_ID, async (request) => {
641
+ const result = await this.dispatchAutomationMessage(paths, request.targetAgentId, request.sessionRef, request.message);
642
+ return {
643
+ kind: request.kind,
644
+ targetAgentId: request.targetAgentId,
645
+ sessionRef: request.sessionRef,
646
+ taskId: request.taskId,
647
+ message: request.message,
590
648
  ok: result.ok,
591
649
  error: result.error,
592
- });
593
- }
650
+ };
651
+ });
594
652
  return {
595
653
  dispatches,
596
654
  todoTasks,
655
+ pendingTasks,
597
656
  blockedTasks,
598
657
  };
599
658
  }
600
- async dispatchInactiveAgentAutomations(paths, inactiveCandidates, inactiveMinutes, latestCeoProjectPath) {
601
- const dispatches = [];
602
- for (const candidate of inactiveCandidates) {
603
- const sessionRef = buildInactiveSessionRef(candidate.managerAgentId, candidate.subjectAgentId);
659
+ async dispatchInactiveAgentAutomations(paths, inactiveCandidates, inactiveMinutes, notificationTimestamp, maxParallelFlows = 1) {
660
+ return runWithConcurrencyByKey(inactiveCandidates, maxParallelFlows, (candidate) => normalizeAgentId(candidate.managerAgentId) || DEFAULT_AGENT_ID, async (candidate) => {
661
+ const sessionRef = buildNotificationSessionRef(candidate.managerAgentId);
604
662
  const message = buildInactiveAgentMessage({
605
663
  managerAgentId: candidate.managerAgentId,
606
664
  subjectAgentId: candidate.subjectAgentId,
@@ -609,21 +667,20 @@ export class OpenGoatService {
609
667
  directReporteesCount: candidate.directReporteesCount,
610
668
  indirectReporteesCount: candidate.indirectReporteesCount,
611
669
  inactiveMinutes,
670
+ notificationTimestamp,
612
671
  lastActionTimestamp: candidate.lastActionTimestamp,
613
672
  });
614
- const result = await this.dispatchAutomationMessage(paths, candidate.managerAgentId, sessionRef, message, {
615
- cwd: latestCeoProjectPath,
616
- });
617
- dispatches.push({
673
+ const result = await this.dispatchAutomationMessage(paths, candidate.managerAgentId, sessionRef, message);
674
+ return {
618
675
  kind: "inactive",
619
676
  targetAgentId: candidate.managerAgentId,
620
677
  sessionRef,
621
678
  subjectAgentId: candidate.subjectAgentId,
679
+ message,
622
680
  ok: result.ok,
623
681
  error: result.error,
624
- });
625
- }
626
- return dispatches;
682
+ };
683
+ });
627
684
  }
628
685
  async listSkills(agentId = DEFAULT_AGENT_ID) {
629
686
  const paths = this.pathsProvider.getPaths();
@@ -678,7 +735,6 @@ export class OpenGoatService {
678
735
  const paths = this.pathsProvider.getPaths();
679
736
  const prepared = await this.sessionService.prepareRunSession(paths, agentId, {
680
737
  sessionRef: options.sessionRef,
681
- projectPath: options.projectPath,
682
738
  forceNew: options.forceNew,
683
739
  userMessage: "",
684
740
  });
@@ -722,7 +778,7 @@ export class OpenGoatService {
722
778
  const result = await this.orchestrationService.runAgent(paths, agentId, {
723
779
  message,
724
780
  sessionRef,
725
- disableSession: options.disableSession ?? true,
781
+ disableSession: options.disableSession ?? false,
726
782
  cwd: options.cwd,
727
783
  env: process.env,
728
784
  });
@@ -777,7 +833,7 @@ export class OpenGoatService {
777
833
  return;
778
834
  }
779
835
  syncedAgents.add(targetAgentId);
780
- const sync = await this.agentService.ensureAgentWorkspaceRoleSkills(paths, targetAgentId);
836
+ const sync = await this.ensureAgentProviderRoleSkills(paths, targetAgentId);
781
837
  createdPaths.push(...sync.createdPaths);
782
838
  skippedPaths.push(...sync.skippedPaths);
783
839
  removedPaths.push(...sync.removedPaths);
@@ -815,6 +871,7 @@ export class OpenGoatService {
815
871
  for (const legacySkillId of [
816
872
  "board-manager",
817
873
  "board-individual",
874
+ "og-boards",
818
875
  "og-board-manager",
819
876
  "og-board-individual",
820
877
  "manager",
@@ -834,6 +891,18 @@ export class OpenGoatService {
834
891
  removedPaths,
835
892
  };
836
893
  }
894
+ async ensureAgentProviderRoleSkills(paths, rawAgentId) {
895
+ const agentId = normalizeAgentId(rawAgentId);
896
+ if (!agentId) {
897
+ throw new Error("Agent id cannot be empty.");
898
+ }
899
+ const runtimeProfile = await this.providerService.getAgentRuntimeProfile(paths, agentId);
900
+ const managedRoleSkillDirectories = await this.providerService.listProviderRoleSkillDirectories();
901
+ return this.agentService.ensureAgentWorkspaceRoleSkills(paths, agentId, {
902
+ requiredSkillDirectories: runtimeProfile.roleSkillDirectories,
903
+ managedSkillDirectories: managedRoleSkillDirectories,
904
+ });
905
+ }
837
906
  async resolveOpenClawManagedSkillsDir(paths) {
838
907
  if (this.openClawManagedSkillsDirCache !== undefined) {
839
908
  return this.openClawManagedSkillsDirCache;
@@ -843,17 +912,28 @@ export class OpenGoatService {
843
912
  ...process.env,
844
913
  ...(providerConfig?.env ?? {}),
845
914
  };
846
- const skillsList = await this.runOpenClaw(["skills", "list", "--json"], {
847
- env,
848
- });
849
- if (skillsList.code !== 0) {
850
- throw new Error(`OpenClaw skills list failed (exit ${skillsList.code}). ${skillsList.stderr.trim() || skillsList.stdout.trim() || ""}`.trim());
915
+ try {
916
+ const skillsList = await this.runOpenClaw(["skills", "list", "--json"], {
917
+ env,
918
+ });
919
+ if (skillsList.code !== 0) {
920
+ throw new Error(`OpenClaw skills list failed (exit ${skillsList.code}). ${skillsList.stderr.trim() || skillsList.stdout.trim() || ""}`.trim());
921
+ }
922
+ const parsed = parseLooseJson(skillsList.stdout);
923
+ if (parsed === undefined) {
924
+ throw new Error("OpenClaw skills list returned non-JSON output; cannot resolve managed skills directory.");
925
+ }
926
+ const managedSkillsDir = extractManagedSkillsDir(parsed);
927
+ this.openClawManagedSkillsDirCache = managedSkillsDir;
928
+ return managedSkillsDir;
851
929
  }
852
- const parsed = parseLooseJson(skillsList.stdout);
853
- if (parsed === undefined) {
854
- throw new Error("OpenClaw skills list returned non-JSON output; cannot resolve managed skills directory.");
930
+ catch (error) {
931
+ if (!(error instanceof ProviderCommandNotFoundError)) {
932
+ throw error;
933
+ }
855
934
  }
856
- const managedSkillsDir = extractManagedSkillsDir(parsed);
935
+ const skillsStatus = await this.providerService.getOpenClawSkillsStatusViaGateway(paths, env);
936
+ const managedSkillsDir = extractManagedSkillsDir(skillsStatus);
857
937
  this.openClawManagedSkillsDirCache = managedSkillsDir;
858
938
  return managedSkillsDir;
859
939
  }
@@ -862,17 +942,30 @@ export class OpenGoatService {
862
942
  return [];
863
943
  }
864
944
  const env = await this.resolveOpenClawEnv(paths);
865
- const listed = await this.runOpenClaw(["agents", "list", "--json"], {
866
- env,
867
- });
868
- if (listed.code !== 0) {
869
- throw new Error(`OpenClaw agents list failed (exit ${listed.code}). ${listed.stderr.trim() || listed.stdout.trim() || ""}`.trim());
945
+ try {
946
+ const listed = await this.runOpenClaw(["agents", "list", "--json"], {
947
+ env,
948
+ });
949
+ if (listed.code !== 0) {
950
+ throw new Error(`OpenClaw agents list failed (exit ${listed.code}). ${listed.stderr.trim() || listed.stdout.trim() || ""}`.trim());
951
+ }
952
+ const parsed = parseLooseJson(listed.stdout);
953
+ if (parsed === undefined) {
954
+ throw new Error("OpenClaw agents list returned non-JSON output; cannot inspect agents.");
955
+ }
956
+ return extractOpenClawAgents(parsed);
870
957
  }
871
- const parsed = parseLooseJson(listed.stdout);
872
- if (parsed === undefined) {
873
- throw new Error("OpenClaw agents list returned non-JSON output; cannot inspect agents.");
958
+ catch (error) {
959
+ if (!(error instanceof ProviderCommandNotFoundError)) {
960
+ throw error;
961
+ }
874
962
  }
875
- return extractOpenClawAgents(parsed);
963
+ const entries = await this.providerService.listOpenClawAgentsViaGateway(paths, env);
964
+ return entries.map((entry) => ({
965
+ id: entry.id,
966
+ workspace: entry.workspace,
967
+ agentDir: entry.agentDir,
968
+ }));
876
969
  }
877
970
  async addWorkspaceAgentCandidates(paths, candidates, warnings) {
878
971
  try {
@@ -985,23 +1078,32 @@ export class OpenGoatService {
985
1078
  }
986
1079
  const warnings = [];
987
1080
  const env = await this.resolveOpenClawEnv(paths);
988
- const listResult = await this.runOpenClaw(["config", "get", "agents.list"], {
989
- env,
990
- });
991
- if (listResult.code !== 0) {
992
- warnings.push(`OpenClaw config read failed (agents.list, code ${listResult.code}). ${listResult.stderr.trim() || listResult.stdout.trim() || ""}`.trim());
993
- return warnings;
994
- }
995
- const parsed = parseLooseJson(listResult.stdout);
996
- if (parsed === undefined) {
997
- warnings.push("OpenClaw config read returned non-JSON for agents.list; skipping sandbox/tools policy sync.");
998
- return warnings;
1081
+ let entries;
1082
+ try {
1083
+ const listResult = await this.runOpenClaw(["config", "get", "agents.list"], {
1084
+ env,
1085
+ });
1086
+ if (listResult.code !== 0) {
1087
+ warnings.push(`OpenClaw config read failed (agents.list, code ${listResult.code}). ${listResult.stderr.trim() || listResult.stdout.trim() || ""}`.trim());
1088
+ return warnings;
1089
+ }
1090
+ const parsed = parseLooseJson(listResult.stdout);
1091
+ if (parsed === undefined) {
1092
+ warnings.push("OpenClaw config read returned non-JSON for agents.list; skipping sandbox/tools policy sync.");
1093
+ return warnings;
1094
+ }
1095
+ if (!Array.isArray(parsed)) {
1096
+ warnings.push("OpenClaw config agents.list is not an array; skipping sandbox/tools policy sync.");
1097
+ return warnings;
1098
+ }
1099
+ entries = parsed;
999
1100
  }
1000
- if (!Array.isArray(parsed)) {
1001
- warnings.push("OpenClaw config agents.list is not an array; skipping sandbox/tools policy sync.");
1002
- return warnings;
1101
+ catch (error) {
1102
+ if (!(error instanceof ProviderCommandNotFoundError)) {
1103
+ throw error;
1104
+ }
1105
+ return this.providerService.syncOpenClawAgentExecutionPoliciesViaGateway(paths, agentIds, env);
1003
1106
  }
1004
- const entries = parsed;
1005
1107
  const indexById = new Map();
1006
1108
  for (let index = 0; index < entries.length; index += 1) {
1007
1109
  const entry = entries[index];
@@ -1215,15 +1317,6 @@ export class OpenGoatService {
1215
1317
  }
1216
1318
  return inactive;
1217
1319
  }
1218
- async resolveLatestProjectPathForAgent(paths, rawAgentId) {
1219
- const agentId = normalizeAgentId(rawAgentId);
1220
- if (!agentId) {
1221
- return undefined;
1222
- }
1223
- const sessions = await this.sessionService.listSessions(paths, agentId);
1224
- const latestProjectPath = sessions[0]?.projectPath?.trim();
1225
- return latestProjectPath || undefined;
1226
- }
1227
1320
  }
1228
1321
  function asRecord(value) {
1229
1322
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -1264,6 +1357,48 @@ function readStringArray(value) {
1264
1357
  .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
1265
1358
  .filter((entry) => entry.length > 0);
1266
1359
  }
1360
+ async function runWithConcurrency(items, rawConcurrency, worker) {
1361
+ if (items.length === 0) {
1362
+ return [];
1363
+ }
1364
+ const concurrency = Math.max(1, Math.floor(rawConcurrency));
1365
+ const results = new Array(items.length);
1366
+ let nextIndex = 0;
1367
+ const runWorker = async () => {
1368
+ while (true) {
1369
+ const currentIndex = nextIndex;
1370
+ nextIndex += 1;
1371
+ if (currentIndex >= items.length) {
1372
+ return;
1373
+ }
1374
+ results[currentIndex] = await worker(items[currentIndex], currentIndex);
1375
+ }
1376
+ };
1377
+ const workerCount = Math.min(concurrency, items.length);
1378
+ const workers = Array.from({ length: workerCount }, () => runWorker());
1379
+ await Promise.all(workers);
1380
+ return results;
1381
+ }
1382
+ async function runWithConcurrencyByKey(items, rawConcurrency, resolveKey, worker) {
1383
+ if (items.length === 0) {
1384
+ return [];
1385
+ }
1386
+ const grouped = new Map();
1387
+ for (let index = 0; index < items.length; index += 1) {
1388
+ const item = items[index];
1389
+ const key = resolveKey(item, index).trim().toLowerCase() || "default";
1390
+ const bucket = grouped.get(key) ?? [];
1391
+ bucket.push({ item, index });
1392
+ grouped.set(key, bucket);
1393
+ }
1394
+ const results = new Array(items.length);
1395
+ await runWithConcurrency([...grouped.values()], rawConcurrency, async (entries) => {
1396
+ for (const entry of entries) {
1397
+ results[entry.index] = await worker(entry.item, entry.index);
1398
+ }
1399
+ });
1400
+ return results;
1401
+ }
1267
1402
  function parseLooseJson(raw) {
1268
1403
  const trimmed = raw.trim();
1269
1404
  if (!trimmed) {
@@ -1298,6 +1433,20 @@ function parseLooseJson(raw) {
1298
1433
  function dedupeNumbers(values) {
1299
1434
  return [...new Set(values)];
1300
1435
  }
1436
+ function resolveCreateAgentManagerId(agentId, reportsTo) {
1437
+ const normalizedAgentId = normalizeAgentId(agentId);
1438
+ if (isDefaultAgentId(normalizedAgentId)) {
1439
+ return null;
1440
+ }
1441
+ if (reportsTo === null || reportsTo === undefined) {
1442
+ return DEFAULT_AGENT_ID;
1443
+ }
1444
+ const normalizedManagerId = normalizeAgentId(reportsTo);
1445
+ if (!normalizedManagerId || normalizedManagerId === normalizedAgentId) {
1446
+ return DEFAULT_AGENT_ID;
1447
+ }
1448
+ return normalizedManagerId;
1449
+ }
1301
1450
  function collectPluginPathCandidatesFromArgvDir(argvDir) {
1302
1451
  const maxDepth = 8;
1303
1452
  const candidates = [];