@opengoat/core 2026.2.17 → 2026.2.19

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 (61) hide show
  1. package/dist/core/agents/application/agent.service.d.ts +10 -0
  2. package/dist/core/agents/application/agent.service.js +85 -38
  3. package/dist/core/agents/application/agent.service.js.map +1 -1
  4. package/dist/core/boards/application/board.service.d.ts +5 -1
  5. package/dist/core/boards/application/board.service.js +115 -29
  6. package/dist/core/boards/application/board.service.js.map +1 -1
  7. package/dist/core/boards/domain/board.d.ts +1 -2
  8. package/dist/core/opengoat/application/opengoat.service.d.ts +9 -2
  9. package/dist/core/opengoat/application/opengoat.service.helpers.d.ts +64 -0
  10. package/dist/core/opengoat/application/opengoat.service.helpers.js +188 -14
  11. package/dist/core/opengoat/application/opengoat.service.helpers.js.map +1 -1
  12. package/dist/core/opengoat/application/opengoat.service.js +282 -71
  13. package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
  14. package/dist/core/orchestration/application/orchestration.service.js +11 -2
  15. package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
  16. package/dist/core/providers/application/provider.service.d.ts +12 -0
  17. package/dist/core/providers/application/provider.service.js +286 -2
  18. package/dist/core/providers/application/provider.service.js.map +1 -1
  19. package/dist/core/providers/cli-provider.d.ts +1 -0
  20. package/dist/core/providers/cli-provider.js +11 -8
  21. package/dist/core/providers/cli-provider.js.map +1 -1
  22. package/dist/core/providers/openclaw-gateway-rpc.js +70 -17
  23. package/dist/core/providers/openclaw-gateway-rpc.js.map +1 -1
  24. package/dist/core/providers/provider-module.d.ts +4 -0
  25. package/dist/core/providers/provider-module.js +4 -0
  26. package/dist/core/providers/provider-module.js.map +1 -1
  27. package/dist/core/providers/providers/claude-code/index.js +4 -0
  28. package/dist/core/providers/providers/claude-code/index.js.map +1 -1
  29. package/dist/core/providers/providers/claude-code/provider.js +1 -0
  30. package/dist/core/providers/providers/claude-code/provider.js.map +1 -1
  31. package/dist/core/providers/providers/codex/index.js +4 -0
  32. package/dist/core/providers/providers/codex/index.js.map +1 -1
  33. package/dist/core/providers/providers/codex/provider.d.ts +1 -0
  34. package/dist/core/providers/providers/codex/provider.js +29 -0
  35. package/dist/core/providers/providers/codex/provider.js.map +1 -1
  36. package/dist/core/providers/providers/cursor/index.js +4 -0
  37. package/dist/core/providers/providers/cursor/index.js.map +1 -1
  38. package/dist/core/providers/providers/cursor/provider.js +2 -1
  39. package/dist/core/providers/providers/cursor/provider.js.map +1 -1
  40. package/dist/core/providers/providers/gemini-cli/index.js +4 -0
  41. package/dist/core/providers/providers/gemini-cli/index.js.map +1 -1
  42. package/dist/core/providers/providers/gemini-cli/provider.js +9 -1
  43. package/dist/core/providers/providers/gemini-cli/provider.js.map +1 -1
  44. package/dist/core/providers/providers/openclaw/index.js +4 -0
  45. package/dist/core/providers/providers/openclaw/index.js.map +1 -1
  46. package/dist/core/providers/providers/opencode/index.js +4 -0
  47. package/dist/core/providers/providers/opencode/index.js.map +1 -1
  48. package/dist/core/providers/providers/opencode/provider.d.ts +1 -0
  49. package/dist/core/providers/providers/opencode/provider.js +13 -0
  50. package/dist/core/providers/providers/opencode/provider.js.map +1 -1
  51. package/dist/core/providers/registry.js +26 -0
  52. package/dist/core/providers/registry.js.map +1 -1
  53. package/dist/core/providers/types.d.ts +1 -0
  54. package/dist/core/sessions/application/session.service.js +17 -0
  55. package/dist/core/sessions/application/session.service.js.map +1 -1
  56. package/dist/core/templates/assets/ceo/ROLE.md +1 -1
  57. package/dist/core/templates/assets/skills/og-board-manager/SKILL.md +3 -6
  58. package/dist/core/templates/default-templates.d.ts +1 -1
  59. package/dist/core/templates/default-templates.js +8 -2
  60. package/dist/core/templates/default-templates.js.map +1 -1
  61. package/package.json +1 -1
@@ -10,11 +10,12 @@ 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, buildNotificationSessionRef, buildPendingTaskMessage, buildReporteeStats, buildTodoTaskMessage, collectAllReportees, containsAgentNotFoundMessage, containsAlreadyExistsMessage, extractManagedSkillsDir, extractOpenClawAgents, isSpawnPermissionOrMissing, pathIsWithin, pathMatches, prepareOpenClawCommandEnv, resolveInactiveAgentNotificationTarget, resolveInactiveMinutes, resolveMaxParallelFlows, toErrorMessage, } from "./opengoat.service.helpers.js";
13
+ import { assertAgentExists, buildBlockedTaskMessage, buildDoingTaskMessage, buildInactiveAgentsMessage, buildNotificationSessionRef, buildPendingTaskMessage, buildReporteeStats, buildTopDownTaskDelegationMessage, buildTodoTaskMessage, collectAllReportees, containsAgentNotFoundMessage, containsAlreadyExistsMessage, extractManagedSkillsDir, extractOpenClawAgents, isSpawnPermissionOrMissing, pathIsWithin, pathMatches, prepareOpenClawCommandEnv, resolveInProgressTimeoutMinutes, resolveBottomUpTaskDelegationStrategy, resolveTopDownTaskDelegationStrategy, 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";
17
17
  const OPENCLAW_AGENT_TOOLS_ALLOW_ALL_JSON = "[\"*\"]";
18
+ const OPENCLAW_AGENT_SKIP_BOOTSTRAP = true;
18
19
  const OPENCLAW_OPENGOAT_PLUGIN_ID = "openclaw-plugin";
19
20
  const OPENCLAW_OPENGOAT_PLUGIN_ROOT_ID = "opengoat-plugin";
20
21
  const OPENCLAW_OPENGOAT_PLUGIN_LEGACY_PACK_ID = "openclaw-plugin-pack";
@@ -97,8 +98,10 @@ export class OpenGoatService {
97
98
  agentManifestService: this.agentManifestService,
98
99
  });
99
100
  }
100
- initialize() {
101
- return this.initializeRuntimeDefaults();
101
+ initialize(options = {}) {
102
+ return this.initializeRuntimeDefaults({
103
+ syncRuntimeDefaults: options.syncRuntimeDefaults ?? true,
104
+ });
102
105
  }
103
106
  async hardReset() {
104
107
  const paths = this.pathsProvider.getPaths();
@@ -203,23 +206,31 @@ export class OpenGoatService {
203
206
  warnings.push(`OpenClaw role skill assignment sync for "ceo" failed: ${toErrorMessage(error)}`);
204
207
  }
205
208
  let openClawAgentEntriesById;
209
+ let openClawInventoryAvailable = false;
206
210
  try {
207
211
  openClawAgentEntriesById = new Map((await this.listOpenClawAgents(paths)).map((entry) => [entry.id, entry]));
212
+ openClawInventoryAvailable = true;
208
213
  }
209
214
  catch (error) {
210
215
  warnings.push(`OpenClaw startup inventory check failed: ${toErrorMessage(error)}`);
211
216
  }
212
217
  try {
213
218
  localAgents = await this.agentService.listAgents(paths);
214
- for (const agent of localAgents) {
215
- const sync = await this.syncOpenClawAgentRegistration(paths, {
216
- descriptor: agent,
217
- existingEntry: openClawAgentEntriesById?.get(agent.id),
218
- });
219
- warnings.push(...sync.warnings);
220
- if (agent.id === DEFAULT_AGENT_ID) {
221
- ceoSynced = sync.synced;
222
- ceoSyncCode = sync.code;
219
+ if (!openClawInventoryAvailable) {
220
+ warnings.push("OpenClaw startup registration sync skipped because agent inventory is unavailable.");
221
+ ceoSynced = localAgents.some((agent) => agent.id === DEFAULT_AGENT_ID);
222
+ }
223
+ else {
224
+ for (const agent of localAgents) {
225
+ const sync = await this.syncOpenClawAgentRegistration(paths, {
226
+ descriptor: agent,
227
+ existingEntry: openClawAgentEntriesById?.get(agent.id),
228
+ });
229
+ warnings.push(...sync.warnings);
230
+ if (agent.id === DEFAULT_AGENT_ID) {
231
+ ceoSynced = sync.synced;
232
+ ceoSyncCode = sync.code;
233
+ }
223
234
  }
224
235
  }
225
236
  }
@@ -306,24 +317,23 @@ export class OpenGoatService {
306
317
  throw new Error(`OpenClaw agent location sync failed for "${created.agent.id}". ${toErrorMessage(error)}`);
307
318
  }
308
319
  await this.syncOpenClawAgentExecutionPolicies(paths, [created.agent.id]);
309
- try {
310
- const workspaceBootstrap = await this.agentService.ensureAgentWorkspaceBootstrap(paths, {
311
- agentId: created.agent.id,
312
- displayName: created.agent.displayName,
313
- role: options.role?.trim() ??
314
- (created.alreadyExisted ? created.agent.role : ""),
315
- }, {
316
- syncBootstrapMarkdown: false,
317
- });
318
- created.createdPaths.push(...workspaceBootstrap.createdPaths);
319
- created.skippedPaths.push(...workspaceBootstrap.skippedPaths);
320
- created.skippedPaths.push(...workspaceBootstrap.removedPaths);
321
- }
322
- catch (error) {
323
- if (!created.alreadyExisted) {
320
+ if (!created.alreadyExisted) {
321
+ try {
322
+ const workspaceBootstrap = await this.agentService.ensureAgentWorkspaceBootstrap(paths, {
323
+ agentId: created.agent.id,
324
+ displayName: created.agent.displayName,
325
+ role: options.role?.trim() ?? "",
326
+ }, {
327
+ syncBootstrapMarkdown: false,
328
+ });
329
+ created.createdPaths.push(...workspaceBootstrap.createdPaths);
330
+ created.skippedPaths.push(...workspaceBootstrap.skippedPaths);
331
+ created.skippedPaths.push(...workspaceBootstrap.removedPaths);
332
+ }
333
+ catch (error) {
324
334
  await this.agentService.removeAgent(paths, created.agent.id);
335
+ throw new Error(`Failed to update workspace bootstrap for "${created.agent.id}". ${toErrorMessage(error)}`);
325
336
  }
326
- throw new Error(`Failed to update workspace bootstrap for "${created.agent.id}". ${toErrorMessage(error)}`);
327
337
  }
328
338
  return {
329
339
  ...created,
@@ -363,6 +373,10 @@ export class OpenGoatService {
363
373
  };
364
374
  }
365
375
  async setAgentManager(rawAgentId, rawReportsTo) {
376
+ const managerAgentId = resolveCreateAgentManagerId(rawAgentId, rawReportsTo);
377
+ if (managerAgentId) {
378
+ await this.assertManagerSupportsReportees(managerAgentId);
379
+ }
366
380
  const paths = this.pathsProvider.getPaths();
367
381
  const updated = await this.agentService.setAgentManager(paths, rawAgentId, rawReportsTo);
368
382
  await this.syncOpenClawRoleSkills(paths, updated.agentId);
@@ -538,6 +552,9 @@ export class OpenGoatService {
538
552
  if (!provider) {
539
553
  throw new Error(`Provider "${managerBinding.providerId}" is not available for manager "${managerAgentId}".`);
540
554
  }
555
+ if (provider.id !== OPENCLAW_PROVIDER_ID) {
556
+ throw new Error(`Cannot assign "${managerAgentId}" as manager because only OpenClaw agents can be managers (found provider "${provider.displayName}").`);
557
+ }
541
558
  if (!provider.capabilities.reportees) {
542
559
  throw new Error(`Cannot assign "${managerAgentId}" as manager because provider "${provider.displayName}" does not support reportees.`);
543
560
  }
@@ -546,18 +563,23 @@ export class OpenGoatService {
546
563
  const paths = this.pathsProvider.getPaths();
547
564
  const ranAt = this.resolveNowIso();
548
565
  const manifests = await this.agentManifestService.listManifests(paths);
549
- const inactiveMinutes = resolveInactiveMinutes(options.inactiveMinutes);
550
- const notificationTarget = resolveInactiveAgentNotificationTarget(options.notificationTarget);
551
- const notifyInactiveAgents = options.notifyInactiveAgents ?? true;
566
+ const inProgressMinutes = resolveInProgressTimeoutMinutes(options.inProgressMinutes);
567
+ const topDownStrategy = resolveTopDownTaskDelegationStrategy(options);
568
+ const bottomUpStrategy = resolveBottomUpTaskDelegationStrategy(options);
552
569
  const maxParallelFlows = resolveMaxParallelFlows(options.maxParallelFlows);
553
- const inactiveCandidates = notifyInactiveAgents
554
- ? await this.collectInactiveAgents(paths, manifests, inactiveMinutes, notificationTarget)
570
+ const inactiveCandidates = bottomUpStrategy.enabled
571
+ ? await this.collectInactiveAgents(paths, manifests, bottomUpStrategy.inactiveMinutes, bottomUpStrategy.notificationTarget)
555
572
  : [];
556
573
  const tasks = await this.boardService.listTasks(paths, { limit: 10_000 });
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);
574
+ const topDownDispatches = topDownStrategy.enabled
575
+ ? await this.dispatchTopDownTaskDelegationAutomations(paths, tasks, manifests, topDownStrategy.openTasksThreshold, ranAt, maxParallelFlows)
576
+ : [];
577
+ const pendingTaskIds = new Set(await this.boardService.listPendingTaskIdsOlderThan(paths, bottomUpStrategy.inactiveMinutes));
578
+ const doingTaskIds = new Set(await this.boardService.listDoingTaskIdsOlderThan(paths, inProgressMinutes));
579
+ const taskStatusDispatch = await this.dispatchTaskStatusAutomations(paths, tasks, manifests, doingTaskIds, pendingTaskIds, inProgressMinutes, bottomUpStrategy.inactiveMinutes, ranAt, maxParallelFlows);
580
+ const inactiveDispatches = await this.dispatchInactiveAgentAutomations(paths, inactiveCandidates, bottomUpStrategy.inactiveMinutes, ranAt, maxParallelFlows);
560
581
  const dispatches = [
582
+ ...topDownDispatches,
561
583
  ...taskStatusDispatch.dispatches,
562
584
  ...inactiveDispatches,
563
585
  ];
@@ -566,6 +588,7 @@ export class OpenGoatService {
566
588
  ranAt,
567
589
  scannedTasks: tasks.length,
568
590
  todoTasks: taskStatusDispatch.todoTasks,
591
+ doingTasks: taskStatusDispatch.doingTasks,
569
592
  blockedTasks: taskStatusDispatch.blockedTasks,
570
593
  inactiveAgents: inactiveCandidates.length,
571
594
  sent: dispatches.length - failed,
@@ -573,10 +596,67 @@ export class OpenGoatService {
573
596
  dispatches,
574
597
  };
575
598
  }
576
- async dispatchTaskStatusAutomations(paths, tasks, manifests, pendingTaskIds, pendingMinutes, notificationTimestamp, maxParallelFlows) {
599
+ async dispatchTopDownTaskDelegationAutomations(paths, tasks, manifests, openTasksThreshold, notificationTimestamp, maxParallelFlows = 1) {
600
+ const openTasks = tasks
601
+ .filter((task) => task.status.trim().toLowerCase() !== "done")
602
+ .sort((left, right) => {
603
+ const leftTimestamp = Date.parse(left.updatedAt);
604
+ const rightTimestamp = Date.parse(right.updatedAt);
605
+ if (Number.isFinite(leftTimestamp) && Number.isFinite(rightTimestamp)) {
606
+ return rightTimestamp - leftTimestamp;
607
+ }
608
+ if (Number.isFinite(rightTimestamp)) {
609
+ return 1;
610
+ }
611
+ if (Number.isFinite(leftTimestamp)) {
612
+ return -1;
613
+ }
614
+ return right.taskId.localeCompare(left.taskId);
615
+ });
616
+ if (openTasks.length > openTasksThreshold) {
617
+ return [];
618
+ }
619
+ const managerAgents = manifests.filter((manifest) => manifest.metadata.type === "manager").length;
620
+ const ceoDirectReportees = manifests.filter((manifest) => normalizeAgentId(manifest.metadata.reportsTo ?? "") === DEFAULT_AGENT_ID).length;
621
+ const sessionRef = buildNotificationSessionRef(DEFAULT_AGENT_ID);
622
+ const message = buildTopDownTaskDelegationMessage({
623
+ openTasksThreshold,
624
+ openTasksCount: openTasks.length,
625
+ totalAgents: manifests.length,
626
+ managerAgents,
627
+ ceoDirectReportees,
628
+ openTasks: openTasks.map((task) => ({
629
+ taskId: task.taskId,
630
+ title: task.title,
631
+ status: task.status,
632
+ assignedTo: task.assignedTo,
633
+ })),
634
+ notificationTimestamp,
635
+ });
636
+ const requests = [
637
+ {
638
+ targetAgentId: DEFAULT_AGENT_ID,
639
+ sessionRef,
640
+ message,
641
+ },
642
+ ];
643
+ return runWithConcurrencyByKey(requests, maxParallelFlows, (request) => request.targetAgentId, async (request) => {
644
+ const result = await this.dispatchAutomationMessage(paths, request.targetAgentId, request.sessionRef, request.message);
645
+ return {
646
+ kind: "topdown",
647
+ targetAgentId: request.targetAgentId,
648
+ sessionRef: request.sessionRef,
649
+ message: request.message,
650
+ ok: result.ok,
651
+ error: result.error,
652
+ };
653
+ });
654
+ }
655
+ async dispatchTaskStatusAutomations(paths, tasks, manifests, doingTaskIds, pendingTaskIds, doingMinutes, pendingMinutes, notificationTimestamp, maxParallelFlows) {
577
656
  const manifestsById = new Map(manifests.map((manifest) => [manifest.agentId, manifest]));
578
657
  const requests = [];
579
658
  let todoTasks = 0;
659
+ let doingTasks = 0;
580
660
  let pendingTasks = 0;
581
661
  let blockedTasks = 0;
582
662
  for (const task of tasks) {
@@ -597,6 +677,27 @@ export class OpenGoatService {
597
677
  });
598
678
  continue;
599
679
  }
680
+ if (task.status === "doing") {
681
+ if (!doingTaskIds.has(task.taskId)) {
682
+ continue;
683
+ }
684
+ doingTasks += 1;
685
+ const targetAgentId = task.assignedTo;
686
+ const sessionRef = buildNotificationSessionRef(targetAgentId);
687
+ const message = buildDoingTaskMessage({
688
+ task,
689
+ doingMinutes,
690
+ notificationTimestamp,
691
+ });
692
+ requests.push({
693
+ kind: "doing",
694
+ targetAgentId,
695
+ sessionRef,
696
+ taskId: task.taskId,
697
+ message,
698
+ });
699
+ continue;
700
+ }
600
701
  if (task.status === "pending") {
601
702
  if (!pendingTaskIds.has(task.taskId)) {
602
703
  continue;
@@ -649,34 +750,55 @@ export class OpenGoatService {
649
750
  error: result.error,
650
751
  };
651
752
  });
753
+ const notifiedDoingTaskIds = dedupeStrings(dispatches
754
+ .filter((dispatch) => dispatch.kind === "doing" && dispatch.ok)
755
+ .map((dispatch) => dispatch.taskId)
756
+ .filter((taskId) => typeof taskId === "string"));
757
+ await Promise.all(notifiedDoingTaskIds.map(async (taskId) => {
758
+ await this.boardService.resetTaskStatusTimeout(paths, taskId, "doing");
759
+ }));
652
760
  return {
653
761
  dispatches,
654
762
  todoTasks,
763
+ doingTasks,
655
764
  pendingTasks,
656
765
  blockedTasks,
657
766
  };
658
767
  }
659
768
  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);
662
- const message = buildInactiveAgentMessage({
663
- managerAgentId: candidate.managerAgentId,
664
- subjectAgentId: candidate.subjectAgentId,
665
- subjectName: candidate.subjectName,
666
- role: candidate.role,
667
- directReporteesCount: candidate.directReporteesCount,
668
- indirectReporteesCount: candidate.indirectReporteesCount,
669
- inactiveMinutes,
670
- notificationTimestamp,
671
- lastActionTimestamp: candidate.lastActionTimestamp,
672
- });
673
- const result = await this.dispatchAutomationMessage(paths, candidate.managerAgentId, sessionRef, message);
769
+ const groupedCandidatesByManager = new Map();
770
+ for (const candidate of inactiveCandidates) {
771
+ const managerAgentId = normalizeAgentId(candidate.managerAgentId) || DEFAULT_AGENT_ID;
772
+ const bucket = groupedCandidatesByManager.get(managerAgentId) ?? [];
773
+ bucket.push(candidate);
774
+ groupedCandidatesByManager.set(managerAgentId, bucket);
775
+ }
776
+ const requests = [...groupedCandidatesByManager.entries()]
777
+ .sort(([leftManagerId], [rightManagerId]) => leftManagerId.localeCompare(rightManagerId))
778
+ .map(([managerAgentId, candidates]) => {
779
+ const orderedCandidates = [...candidates].sort((left, right) => left.subjectAgentId.localeCompare(right.subjectAgentId));
780
+ return {
781
+ managerAgentId,
782
+ candidates: orderedCandidates,
783
+ sessionRef: buildNotificationSessionRef(managerAgentId),
784
+ message: buildInactiveAgentsMessage({
785
+ inactiveMinutes,
786
+ notificationTimestamp,
787
+ candidates: orderedCandidates,
788
+ }),
789
+ subjectAgentId: orderedCandidates.length === 1
790
+ ? orderedCandidates[0]?.subjectAgentId
791
+ : undefined,
792
+ };
793
+ });
794
+ return runWithConcurrencyByKey(requests, maxParallelFlows, (request) => request.managerAgentId, async (request) => {
795
+ const result = await this.dispatchAutomationMessage(paths, request.managerAgentId, request.sessionRef, request.message);
674
796
  return {
675
797
  kind: "inactive",
676
- targetAgentId: candidate.managerAgentId,
677
- sessionRef,
678
- subjectAgentId: candidate.subjectAgentId,
679
- message,
798
+ targetAgentId: request.managerAgentId,
799
+ sessionRef: request.sessionRef,
800
+ subjectAgentId: request.subjectAgentId,
801
+ message: request.message,
680
802
  ok: result.ok,
681
803
  error: result.error,
682
804
  };
@@ -773,9 +895,9 @@ export class OpenGoatService {
773
895
  getPaths() {
774
896
  return this.pathsProvider.getPaths();
775
897
  }
776
- async dispatchAutomationMessage(paths, agentId, sessionRef, message, options = {}) {
898
+ async dispatchAutomationMessage(_paths, agentId, sessionRef, message, options = {}) {
777
899
  try {
778
- const result = await this.orchestrationService.runAgent(paths, agentId, {
900
+ const result = await this.runAgent(agentId, {
779
901
  message,
780
902
  sessionRef,
781
903
  disableSession: options.disableSession ?? false,
@@ -802,13 +924,15 @@ export class OpenGoatService {
802
924
  resolveNowIso() {
803
925
  return this.nowIso();
804
926
  }
805
- async initializeRuntimeDefaults() {
927
+ async initializeRuntimeDefaults(options) {
806
928
  const initialization = await this.bootstrapService.initialize();
807
- try {
808
- await this.syncRuntimeDefaults();
809
- }
810
- catch {
811
- // Startup remains functional even if OpenClaw CLI/runtime is unavailable.
929
+ if (options.syncRuntimeDefaults) {
930
+ try {
931
+ await this.syncRuntimeDefaults();
932
+ }
933
+ catch {
934
+ // Startup remains functional even if OpenClaw CLI/runtime is unavailable.
935
+ }
812
936
  }
813
937
  return initialization;
814
938
  }
@@ -898,9 +1022,12 @@ export class OpenGoatService {
898
1022
  }
899
1023
  const runtimeProfile = await this.providerService.getAgentRuntimeProfile(paths, agentId);
900
1024
  const managedRoleSkillDirectories = await this.providerService.listProviderRoleSkillDirectories();
1025
+ const managedRoleSkillIds = await this.providerService.listProviderRoleSkillIds();
901
1026
  return this.agentService.ensureAgentWorkspaceRoleSkills(paths, agentId, {
902
1027
  requiredSkillDirectories: runtimeProfile.roleSkillDirectories,
903
1028
  managedSkillDirectories: managedRoleSkillDirectories,
1029
+ roleSkillIdsByType: runtimeProfile.roleSkillIds,
1030
+ managedRoleSkillIds,
904
1031
  });
905
1032
  }
906
1033
  async resolveOpenClawManagedSkillsDir(paths) {
@@ -1135,6 +1262,12 @@ export class OpenGoatService {
1135
1262
  warnings.push(`OpenClaw tools policy sync failed for "${agentId}" (code ${toolsSet.code}). ${toolsSet.stderr.trim() || toolsSet.stdout.trim() || ""}`.trim());
1136
1263
  }
1137
1264
  }
1265
+ if (readAgentSkipBootstrap(entry) !== OPENCLAW_AGENT_SKIP_BOOTSTRAP) {
1266
+ const bootstrapSet = await this.runOpenClaw(["config", "set", `agents.list[${index}].skipBootstrap`, "true"], { env });
1267
+ if (bootstrapSet.code !== 0) {
1268
+ warnings.push(`OpenClaw bootstrap policy sync failed for "${agentId}" (code ${bootstrapSet.code}). ${bootstrapSet.stderr.trim() || bootstrapSet.stdout.trim() || ""}`.trim());
1269
+ }
1270
+ }
1138
1271
  }
1139
1272
  return warnings;
1140
1273
  }
@@ -1349,6 +1482,22 @@ function hasAgentToolsAllowAll(entry) {
1349
1482
  }
1350
1483
  return allow.some((value) => typeof value === "string" && value.trim() === "*");
1351
1484
  }
1485
+ function readAgentSkipBootstrap(entry) {
1486
+ const value = entry.skipBootstrap;
1487
+ if (typeof value === "boolean") {
1488
+ return value;
1489
+ }
1490
+ if (typeof value === "string") {
1491
+ const normalized = value.trim().toLowerCase();
1492
+ if (normalized === "true") {
1493
+ return true;
1494
+ }
1495
+ if (normalized === "false") {
1496
+ return false;
1497
+ }
1498
+ }
1499
+ return undefined;
1500
+ }
1352
1501
  function readStringArray(value) {
1353
1502
  if (!Array.isArray(value)) {
1354
1503
  return [];
@@ -1404,11 +1553,15 @@ function parseLooseJson(raw) {
1404
1553
  if (!trimmed) {
1405
1554
  return undefined;
1406
1555
  }
1407
- try {
1408
- return JSON.parse(trimmed);
1556
+ const exact = tryParseJson(trimmed);
1557
+ if (exact !== undefined) {
1558
+ return exact;
1409
1559
  }
1410
- catch {
1411
- // continue
1560
+ for (const line of trimmed.split(/\r?\n/)) {
1561
+ const parsedLine = tryParseJson(line.trim());
1562
+ if (parsedLine !== undefined) {
1563
+ return parsedLine;
1564
+ }
1412
1565
  }
1413
1566
  const starts = dedupeNumbers([
1414
1567
  trimmed.indexOf("{"),
@@ -1421,11 +1574,69 @@ function parseLooseJson(raw) {
1421
1574
  if (!candidate) {
1422
1575
  continue;
1423
1576
  }
1424
- try {
1425
- return JSON.parse(candidate);
1577
+ const parsedCandidate = tryParseJson(candidate);
1578
+ if (parsedCandidate !== undefined) {
1579
+ return parsedCandidate;
1426
1580
  }
1427
- catch {
1428
- // keep trying candidates
1581
+ const balancedCandidate = extractBalancedJsonCandidate(trimmed, startIndex);
1582
+ if (!balancedCandidate) {
1583
+ continue;
1584
+ }
1585
+ const parsedBalancedCandidate = tryParseJson(balancedCandidate);
1586
+ if (parsedBalancedCandidate !== undefined) {
1587
+ return parsedBalancedCandidate;
1588
+ }
1589
+ }
1590
+ return undefined;
1591
+ }
1592
+ function tryParseJson(raw) {
1593
+ if (!raw) {
1594
+ return undefined;
1595
+ }
1596
+ try {
1597
+ return JSON.parse(raw);
1598
+ }
1599
+ catch {
1600
+ return undefined;
1601
+ }
1602
+ }
1603
+ function extractBalancedJsonCandidate(raw, startIndex) {
1604
+ const opening = raw[startIndex];
1605
+ if (opening !== "{" && opening !== "[") {
1606
+ return undefined;
1607
+ }
1608
+ const closing = opening === "{" ? "}" : "]";
1609
+ let depth = 0;
1610
+ let inString = false;
1611
+ let escaping = false;
1612
+ for (let index = startIndex; index < raw.length; index += 1) {
1613
+ const char = raw[index];
1614
+ if (inString) {
1615
+ if (escaping) {
1616
+ escaping = false;
1617
+ }
1618
+ else if (char === "\\") {
1619
+ escaping = true;
1620
+ }
1621
+ else if (char === "\"") {
1622
+ inString = false;
1623
+ }
1624
+ continue;
1625
+ }
1626
+ if (char === "\"") {
1627
+ inString = true;
1628
+ continue;
1629
+ }
1630
+ if (char === opening) {
1631
+ depth += 1;
1632
+ continue;
1633
+ }
1634
+ if (char !== closing) {
1635
+ continue;
1636
+ }
1637
+ depth -= 1;
1638
+ if (depth === 0) {
1639
+ return raw.slice(startIndex, index + 1).trim();
1429
1640
  }
1430
1641
  }
1431
1642
  return undefined;