@opengoat/core 2026.2.14 → 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 (89) hide show
  1. package/dist/core/agents/application/agent.service.d.ts +19 -3
  2. package/dist/core/agents/application/agent.service.js +170 -45
  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 +17 -1
  8. package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -1
  9. package/dist/core/opengoat/application/opengoat.service.d.ts +14 -3
  10. package/dist/core/opengoat/application/opengoat.service.helpers.d.ts +62 -0
  11. package/dist/core/opengoat/application/opengoat.service.helpers.js +403 -0
  12. package/dist/core/opengoat/application/opengoat.service.helpers.js.map +1 -0
  13. package/dist/core/opengoat/application/opengoat.service.js +653 -371
  14. package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
  15. package/dist/core/orchestration/application/orchestration.service.d.ts +2 -0
  16. package/dist/core/orchestration/application/orchestration.service.js +150 -52
  17. package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
  18. package/dist/core/providers/application/provider.service.d.ts +35 -4
  19. package/dist/core/providers/application/provider.service.js +823 -96
  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 +5 -3
  83. package/dist/core/templates/assets/skills/og-board-individual/SKILL.md +66 -89
  84. package/dist/core/templates/assets/skills/og-board-manager/SKILL.md +64 -45
  85. package/dist/core/templates/assets/skills/og-boards/SKILL.md +65 -0
  86. package/dist/core/templates/default-templates.d.ts +1 -2
  87. package/dist/core/templates/default-templates.js +22 -10
  88. package/dist/core/templates/default-templates.js.map +1 -1
  89. package/package.json +3 -2
@@ -1,18 +1,24 @@
1
- import { homedir } from "node:os";
2
- import path from "node:path";
3
1
  import { AgentManifestService } from "../../agents/application/agent-manifest.service.js";
4
2
  import { AgentService } from "../../agents/application/agent.service.js";
5
3
  import { BoardService, } from "../../boards/index.js";
6
4
  import { BootstrapService } from "../../bootstrap/application/bootstrap.service.js";
7
- import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../domain/agent-id.js";
5
+ import { DEFAULT_AGENT_ID, isDefaultAgentId, normalizeAgentId, } from "../../domain/agent-id.js";
8
6
  import { createNoopLogger } from "../../logging/index.js";
9
7
  import { OrchestrationService, } from "../../orchestration/index.js";
10
8
  import { ProviderService } from "../../providers/application/provider.service.js";
11
9
  import { ProviderCommandNotFoundError, createDefaultProviderRegistry, } from "../../providers/index.js";
10
+ import { dirname, resolve as resolvePath } from "node:path";
12
11
  import { SessionService, } from "../../sessions/index.js";
13
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";
14
14
  const OPENCLAW_PROVIDER_ID = "openclaw";
15
15
  const OPENCLAW_DEFAULT_AGENT_ID = "main";
16
+ const OPENCLAW_AGENT_SANDBOX_MODE = "off";
17
+ const OPENCLAW_AGENT_TOOLS_ALLOW_ALL_JSON = "[\"*\"]";
18
+ const OPENCLAW_OPENGOAT_PLUGIN_ID = "openclaw-plugin";
19
+ const OPENCLAW_OPENGOAT_PLUGIN_ROOT_ID = "opengoat-plugin";
20
+ const OPENCLAW_OPENGOAT_PLUGIN_LEGACY_PACK_ID = "openclaw-plugin-pack";
21
+ const OPENCLAW_OPENGOAT_PLUGIN_FALLBACK_ID = "workspace";
16
22
  export class OpenGoatService {
17
23
  fileSystem;
18
24
  pathPort;
@@ -178,9 +184,10 @@ export class OpenGoatService {
178
184
  const warnings = [];
179
185
  let ceoSynced = false;
180
186
  let ceoSyncCode;
181
- let ceoDescriptor = (await this.agentService.listAgents(paths)).find((agent) => agent.id === DEFAULT_AGENT_ID);
182
- if (!ceoDescriptor) {
183
- const created = await this.agentService.ensureAgent(paths, {
187
+ let localAgents = [];
188
+ const ceoExists = (await this.agentService.listAgents(paths)).some((agent) => agent.id === DEFAULT_AGENT_ID);
189
+ if (!ceoExists) {
190
+ await this.agentService.ensureAgent(paths, {
184
191
  id: DEFAULT_AGENT_ID,
185
192
  displayName: "CEO",
186
193
  }, {
@@ -188,7 +195,6 @@ export class OpenGoatService {
188
195
  reportsTo: null,
189
196
  role: "CEO",
190
197
  });
191
- ceoDescriptor = created.agent;
192
198
  }
193
199
  try {
194
200
  await this.syncOpenClawRoleSkills(paths, DEFAULT_AGENT_ID);
@@ -196,42 +202,50 @@ export class OpenGoatService {
196
202
  catch (error) {
197
203
  warnings.push(`OpenClaw role skill assignment sync for "ceo" failed: ${toErrorMessage(error)}`);
198
204
  }
205
+ let openClawAgentEntriesById;
199
206
  try {
200
- const ceoSync = await this.providerService.createProviderAgent(paths, DEFAULT_AGENT_ID, {
201
- providerId: OPENCLAW_PROVIDER_ID,
202
- displayName: ceoDescriptor.displayName,
203
- workspaceDir: ceoDescriptor.workspaceDir,
204
- internalConfigDir: ceoDescriptor.internalConfigDir,
205
- });
206
- ceoSyncCode = ceoSync.code;
207
- ceoSynced =
208
- ceoSync.code === 0 ||
209
- containsAlreadyExistsMessage(ceoSync.stdout, ceoSync.stderr);
210
- if (!ceoSynced) {
211
- warnings.push(`OpenClaw sync for "ceo" failed (code ${ceoSync.code}). ${(ceoSync.stderr || ceoSync.stdout).trim()}`);
212
- }
207
+ openClawAgentEntriesById = new Map((await this.listOpenClawAgents(paths)).map((entry) => [entry.id, entry]));
213
208
  }
214
209
  catch (error) {
215
- warnings.push(`OpenClaw sync for "ceo" failed: ${toErrorMessage(error)}`);
210
+ warnings.push(`OpenClaw startup inventory check failed: ${toErrorMessage(error)}`);
216
211
  }
217
- if (ceoSynced) {
218
- try {
219
- await this.ensureOpenClawAgentLocation(paths, {
220
- agentId: DEFAULT_AGENT_ID,
221
- displayName: ceoDescriptor.displayName,
222
- workspaceDir: ceoDescriptor.workspaceDir,
223
- internalConfigDir: ceoDescriptor.internalConfigDir,
212
+ try {
213
+ 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),
224
218
  });
219
+ warnings.push(...sync.warnings);
220
+ if (agent.id === DEFAULT_AGENT_ID) {
221
+ ceoSynced = sync.synced;
222
+ ceoSyncCode = sync.code;
223
+ }
225
224
  }
226
- catch (error) {
227
- warnings.push(`OpenClaw ceo location sync failed: ${toErrorMessage(error)}`);
228
- }
225
+ }
226
+ catch (error) {
227
+ warnings.push(`OpenClaw startup sync failed: ${toErrorMessage(error)}`);
228
+ }
229
+ try {
230
+ warnings.push(...(await this.syncOpenClawAgentExecutionPolicies(paths, localAgents.map((agent) => agent.id))));
231
+ }
232
+ catch (error) {
233
+ warnings.push(`OpenClaw agent policy sync failed: ${toErrorMessage(error)}`);
229
234
  }
230
235
  try {
231
- await this.agentService.ensureCeoWorkspaceBootstrap(paths);
236
+ warnings.push(...(await this.ensureOpenGoatPluginToolsRegistered(paths)));
232
237
  }
233
238
  catch (error) {
234
- warnings.push(`OpenGoat workspace bootstrap for "ceo" failed: ${toErrorMessage(error)}`);
239
+ warnings.push(`OpenClaw plugin tool sync failed: ${toErrorMessage(error)}`);
240
+ }
241
+ try {
242
+ const agents = await this.agentService.listAgents(paths);
243
+ for (const agent of agents) {
244
+ await this.agentService.ensureAgentWorkspaceCommandShim(paths, agent.id);
245
+ }
246
+ }
247
+ catch (error) {
248
+ warnings.push(`OpenGoat workspace command shim sync failed: ${toErrorMessage(error)}`);
235
249
  }
236
250
  return {
237
251
  ceoSyncCode,
@@ -241,6 +255,10 @@ export class OpenGoatService {
241
255
  }
242
256
  async createAgent(rawName, options = {}) {
243
257
  const identity = this.agentService.normalizeAgentName(rawName);
258
+ const managerAgentId = resolveCreateAgentManagerId(identity.id, options.reportsTo);
259
+ if (managerAgentId) {
260
+ await this.assertManagerSupportsReportees(managerAgentId);
261
+ }
244
262
  const paths = this.pathsProvider.getPaths();
245
263
  const created = await this.agentService.ensureAgent(paths, identity, {
246
264
  type: options.type,
@@ -287,12 +305,15 @@ export class OpenGoatService {
287
305
  }
288
306
  throw new Error(`OpenClaw agent location sync failed for "${created.agent.id}". ${toErrorMessage(error)}`);
289
307
  }
308
+ await this.syncOpenClawAgentExecutionPolicies(paths, [created.agent.id]);
290
309
  try {
291
310
  const workspaceBootstrap = await this.agentService.ensureAgentWorkspaceBootstrap(paths, {
292
311
  agentId: created.agent.id,
293
312
  displayName: created.agent.displayName,
294
313
  role: options.role?.trim() ??
295
314
  (created.alreadyExisted ? created.agent.role : ""),
315
+ }, {
316
+ syncBootstrapMarkdown: false,
296
317
  });
297
318
  created.createdPaths.push(...workspaceBootstrap.createdPaths);
298
319
  created.skippedPaths.push(...workspaceBootstrap.skippedPaths);
@@ -390,12 +411,13 @@ export class OpenGoatService {
390
411
  this.agentService.listAgents(paths),
391
412
  this.agentManifestService.listManifests(paths),
392
413
  ]);
414
+ const reporteeStats = buildReporteeStats(manifests);
393
415
  const descriptorsById = new Map(agents.map((agent) => [agent.id, agent]));
394
416
  const agent = descriptorsById.get(agentId);
395
417
  if (!agent) {
396
418
  throw new Error(`Agent "${agentId}" does not exist.`);
397
419
  }
398
- const totalReportees = collectAllReportees(manifests, agentId).length;
420
+ const totalReportees = reporteeStats.totalByManager.get(agentId) ?? 0;
399
421
  const directReportees = manifests
400
422
  .filter((manifest) => manifest.metadata.reportsTo === agentId)
401
423
  .map((manifest) => {
@@ -408,8 +430,7 @@ export class OpenGoatService {
408
430
  id: manifest.agentId,
409
431
  name,
410
432
  role,
411
- totalReportees: collectAllReportees(manifests, manifest.agentId)
412
- .length,
433
+ totalReportees: reporteeStats.totalByManager.get(manifest.agentId) ?? 0,
413
434
  };
414
435
  })
415
436
  .sort((left, right) => left.id.localeCompare(right.id));
@@ -445,7 +466,9 @@ export class OpenGoatService {
445
466
  }
446
467
  async setAgentProvider(agentId, providerId) {
447
468
  const paths = this.pathsProvider.getPaths();
448
- 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;
449
472
  }
450
473
  async getOpenClawGatewayConfig() {
451
474
  const paths = this.pathsProvider.getPaths();
@@ -503,94 +526,161 @@ export class OpenGoatService {
503
526
  const paths = this.pathsProvider.getPaths();
504
527
  return this.boardService.addTaskWorklog(paths, actorId, taskId, content);
505
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
+ }
506
545
  async runTaskCronCycle(options = {}) {
507
546
  const paths = this.pathsProvider.getPaths();
508
547
  const ranAt = this.resolveNowIso();
509
548
  const manifests = await this.agentManifestService.listManifests(paths);
510
- const manifestsById = new Map(manifests.map((manifest) => [manifest.agentId, manifest]));
511
549
  const inactiveMinutes = resolveInactiveMinutes(options.inactiveMinutes);
512
550
  const notificationTarget = resolveInactiveAgentNotificationTarget(options.notificationTarget);
513
551
  const notifyInactiveAgents = options.notifyInactiveAgents ?? true;
552
+ const maxParallelFlows = resolveMaxParallelFlows(options.maxParallelFlows);
514
553
  const inactiveCandidates = notifyInactiveAgents
515
554
  ? await this.collectInactiveAgents(paths, manifests, inactiveMinutes, notificationTarget)
516
555
  : [];
517
- const latestCeoProjectPath = notifyInactiveAgents
518
- ? await this.resolveLatestProjectPathForAgent(paths, DEFAULT_AGENT_ID)
519
- : undefined;
520
556
  const tasks = await this.boardService.listTasks(paths, { limit: 10_000 });
521
- const dispatches = [];
522
- let scannedTasks = tasks.length;
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);
560
+ const dispatches = [
561
+ ...taskStatusDispatch.dispatches,
562
+ ...inactiveDispatches,
563
+ ];
564
+ const failed = dispatches.filter((entry) => !entry.ok).length;
565
+ return {
566
+ ranAt,
567
+ scannedTasks: tasks.length,
568
+ todoTasks: taskStatusDispatch.todoTasks,
569
+ blockedTasks: taskStatusDispatch.blockedTasks,
570
+ inactiveAgents: inactiveCandidates.length,
571
+ sent: dispatches.length - failed,
572
+ failed,
573
+ dispatches,
574
+ };
575
+ }
576
+ async dispatchTaskStatusAutomations(paths, tasks, manifests, pendingTaskIds, pendingMinutes, notificationTimestamp, maxParallelFlows) {
577
+ const manifestsById = new Map(manifests.map((manifest) => [manifest.agentId, manifest]));
578
+ const requests = [];
523
579
  let todoTasks = 0;
580
+ let pendingTasks = 0;
524
581
  let blockedTasks = 0;
525
582
  for (const task of tasks) {
526
- if (task.status !== "todo" && task.status !== "blocked") {
527
- continue;
528
- }
529
583
  if (task.status === "todo") {
530
584
  todoTasks += 1;
531
585
  const targetAgentId = task.assignedTo;
532
- const sessionRef = buildTaskSessionRef(targetAgentId, task.taskId);
533
- const message = buildTodoTaskMessage({ task });
534
- const result = await this.dispatchAutomationMessage(paths, targetAgentId, sessionRef, message);
535
- dispatches.push({
586
+ const sessionRef = buildNotificationSessionRef(targetAgentId);
587
+ const message = buildTodoTaskMessage({
588
+ task,
589
+ notificationTimestamp,
590
+ });
591
+ requests.push({
536
592
  kind: "todo",
537
593
  targetAgentId,
538
594
  sessionRef,
539
595
  taskId: task.taskId,
540
- ok: result.ok,
541
- error: result.error,
596
+ message,
542
597
  });
543
598
  continue;
544
599
  }
545
- blockedTasks += 1;
546
- const assigneeManifest = manifestsById.get(task.assignedTo);
547
- const managerAgentId = normalizeAgentId(assigneeManifest?.metadata.reportsTo ?? "") ||
548
- DEFAULT_AGENT_ID;
549
- const sessionRef = buildTaskSessionRef(managerAgentId, task.taskId);
550
- const message = buildBlockedTaskMessage({ task });
551
- const result = await this.dispatchAutomationMessage(paths, managerAgentId, sessionRef, message);
552
- dispatches.push({
553
- kind: "blocked",
554
- targetAgentId: managerAgentId,
555
- sessionRef,
556
- 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,
557
648
  ok: result.ok,
558
649
  error: result.error,
559
- });
560
- }
561
- for (const candidate of inactiveCandidates) {
562
- const sessionRef = buildInactiveSessionRef(candidate.managerAgentId, candidate.subjectAgentId);
650
+ };
651
+ });
652
+ return {
653
+ dispatches,
654
+ todoTasks,
655
+ pendingTasks,
656
+ blockedTasks,
657
+ };
658
+ }
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);
563
662
  const message = buildInactiveAgentMessage({
564
663
  managerAgentId: candidate.managerAgentId,
565
664
  subjectAgentId: candidate.subjectAgentId,
566
665
  subjectName: candidate.subjectName,
567
666
  role: candidate.role,
667
+ directReporteesCount: candidate.directReporteesCount,
668
+ indirectReporteesCount: candidate.indirectReporteesCount,
568
669
  inactiveMinutes,
670
+ notificationTimestamp,
569
671
  lastActionTimestamp: candidate.lastActionTimestamp,
570
672
  });
571
- const result = await this.dispatchAutomationMessage(paths, candidate.managerAgentId, sessionRef, message, {
572
- cwd: latestCeoProjectPath,
573
- });
574
- dispatches.push({
673
+ const result = await this.dispatchAutomationMessage(paths, candidate.managerAgentId, sessionRef, message);
674
+ return {
575
675
  kind: "inactive",
576
676
  targetAgentId: candidate.managerAgentId,
577
677
  sessionRef,
578
678
  subjectAgentId: candidate.subjectAgentId,
679
+ message,
579
680
  ok: result.ok,
580
681
  error: result.error,
581
- });
582
- }
583
- const failed = dispatches.filter((entry) => !entry.ok).length;
584
- return {
585
- ranAt,
586
- scannedTasks,
587
- todoTasks,
588
- blockedTasks,
589
- inactiveAgents: inactiveCandidates.length,
590
- sent: dispatches.length - failed,
591
- failed,
592
- dispatches,
593
- };
682
+ };
683
+ });
594
684
  }
595
685
  async listSkills(agentId = DEFAULT_AGENT_ID) {
596
686
  const paths = this.pathsProvider.getPaths();
@@ -645,7 +735,6 @@ export class OpenGoatService {
645
735
  const paths = this.pathsProvider.getPaths();
646
736
  const prepared = await this.sessionService.prepareRunSession(paths, agentId, {
647
737
  sessionRef: options.sessionRef,
648
- projectPath: options.projectPath,
649
738
  forceNew: options.forceNew,
650
739
  userMessage: "",
651
740
  });
@@ -689,6 +778,7 @@ export class OpenGoatService {
689
778
  const result = await this.orchestrationService.runAgent(paths, agentId, {
690
779
  message,
691
780
  sessionRef,
781
+ disableSession: options.disableSession ?? false,
692
782
  cwd: options.cwd,
693
783
  env: process.env,
694
784
  });
@@ -743,7 +833,7 @@ export class OpenGoatService {
743
833
  return;
744
834
  }
745
835
  syncedAgents.add(targetAgentId);
746
- const sync = await this.agentService.ensureAgentWorkspaceRoleSkills(paths, targetAgentId);
836
+ const sync = await this.ensureAgentProviderRoleSkills(paths, targetAgentId);
747
837
  createdPaths.push(...sync.createdPaths);
748
838
  skippedPaths.push(...sync.skippedPaths);
749
839
  removedPaths.push(...sync.removedPaths);
@@ -781,6 +871,7 @@ export class OpenGoatService {
781
871
  for (const legacySkillId of [
782
872
  "board-manager",
783
873
  "board-individual",
874
+ "og-boards",
784
875
  "og-board-manager",
785
876
  "og-board-individual",
786
877
  "manager",
@@ -800,6 +891,18 @@ export class OpenGoatService {
800
891
  removedPaths,
801
892
  };
802
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
+ }
803
906
  async resolveOpenClawManagedSkillsDir(paths) {
804
907
  if (this.openClawManagedSkillsDirCache !== undefined) {
805
908
  return this.openClawManagedSkillsDirCache;
@@ -809,20 +912,28 @@ export class OpenGoatService {
809
912
  ...process.env,
810
913
  ...(providerConfig?.env ?? {}),
811
914
  };
812
- const skillsList = await this.runOpenClaw(["skills", "list", "--json"], {
813
- env,
814
- });
815
- if (skillsList.code !== 0) {
816
- throw new Error(`OpenClaw skills list failed (exit ${skillsList.code}). ${skillsList.stderr.trim() || skillsList.stdout.trim() || ""}`.trim());
817
- }
818
- let parsed;
819
915
  try {
820
- parsed = JSON.parse(skillsList.stdout);
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;
821
929
  }
822
- catch {
823
- 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
+ }
824
934
  }
825
- const managedSkillsDir = extractManagedSkillsDir(parsed);
935
+ const skillsStatus = await this.providerService.getOpenClawSkillsStatusViaGateway(paths, env);
936
+ const managedSkillsDir = extractManagedSkillsDir(skillsStatus);
826
937
  this.openClawManagedSkillsDirCache = managedSkillsDir;
827
938
  return managedSkillsDir;
828
939
  }
@@ -831,20 +942,30 @@ export class OpenGoatService {
831
942
  return [];
832
943
  }
833
944
  const env = await this.resolveOpenClawEnv(paths);
834
- const listed = await this.runOpenClaw(["agents", "list", "--json"], {
835
- env,
836
- });
837
- if (listed.code !== 0) {
838
- throw new Error(`OpenClaw agents list failed (exit ${listed.code}). ${listed.stderr.trim() || listed.stdout.trim() || ""}`.trim());
839
- }
840
- let parsed;
841
945
  try {
842
- parsed = JSON.parse(listed.stdout);
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);
843
957
  }
844
- catch {
845
- 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
+ }
846
962
  }
847
- 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
+ }));
848
969
  }
849
970
  async addWorkspaceAgentCandidates(paths, candidates, warnings) {
850
971
  try {
@@ -861,25 +982,66 @@ export class OpenGoatService {
861
982
  warnings.push(`Workspace fallback discovery failed: ${toErrorMessage(error)}`);
862
983
  }
863
984
  }
864
- async ensureOpenClawAgentLocation(paths, params) {
865
- if (!this.commandRunner) {
866
- return;
985
+ async syncOpenClawAgentRegistration(paths, params) {
986
+ const warnings = [];
987
+ if (params.existingEntry &&
988
+ pathMatches(params.existingEntry.workspace, params.descriptor.workspaceDir) &&
989
+ pathMatches(params.existingEntry.agentDir, params.descriptor.internalConfigDir)) {
990
+ return {
991
+ synced: true,
992
+ code: 0,
993
+ warnings,
994
+ };
867
995
  }
868
- const env = await this.resolveOpenClawEnv(paths);
869
- const listed = await this.runOpenClaw(["agents", "list", "--json"], {
870
- env,
871
- });
872
- if (listed.code !== 0) {
873
- throw new Error(`OpenClaw agents list failed (exit ${listed.code}). ${listed.stderr.trim() || listed.stdout.trim() || ""}`.trim());
996
+ let runtimeSync;
997
+ try {
998
+ runtimeSync = await this.providerService.createProviderAgent(paths, params.descriptor.id, {
999
+ providerId: OPENCLAW_PROVIDER_ID,
1000
+ displayName: params.descriptor.displayName,
1001
+ workspaceDir: params.descriptor.workspaceDir,
1002
+ internalConfigDir: params.descriptor.internalConfigDir,
1003
+ });
1004
+ }
1005
+ catch (error) {
1006
+ warnings.push(`OpenClaw sync for "${params.descriptor.id}" failed: ${toErrorMessage(error)}`);
1007
+ return {
1008
+ synced: false,
1009
+ warnings,
1010
+ };
1011
+ }
1012
+ const synced = runtimeSync.code === 0 ||
1013
+ containsAlreadyExistsMessage(runtimeSync.stdout, runtimeSync.stderr);
1014
+ if (!synced) {
1015
+ warnings.push(`OpenClaw sync for "${params.descriptor.id}" failed (code ${runtimeSync.code}). ${(runtimeSync.stderr || runtimeSync.stdout).trim()}`);
1016
+ return {
1017
+ synced,
1018
+ code: runtimeSync.code,
1019
+ warnings,
1020
+ };
874
1021
  }
875
- let parsed;
876
1022
  try {
877
- parsed = JSON.parse(listed.stdout);
1023
+ await this.ensureOpenClawAgentLocation(paths, {
1024
+ agentId: params.descriptor.id,
1025
+ displayName: params.descriptor.displayName,
1026
+ workspaceDir: params.descriptor.workspaceDir,
1027
+ internalConfigDir: params.descriptor.internalConfigDir,
1028
+ }, params.existingEntry);
878
1029
  }
879
- catch {
880
- throw new Error("OpenClaw agents list returned non-JSON output; cannot verify agent location.");
1030
+ catch (error) {
1031
+ warnings.push(`OpenClaw location sync for "${params.descriptor.id}" failed: ${toErrorMessage(error)}`);
881
1032
  }
882
- const entry = extractOpenClawAgentEntry(parsed, params.agentId);
1033
+ return {
1034
+ synced,
1035
+ code: runtimeSync.code,
1036
+ warnings,
1037
+ };
1038
+ }
1039
+ async ensureOpenClawAgentLocation(paths, params, existingEntry) {
1040
+ if (!this.commandRunner) {
1041
+ return;
1042
+ }
1043
+ const entry = existingEntry ??
1044
+ (await this.listOpenClawAgents(paths)).find((candidate) => candidate.id === normalizeAgentId(params.agentId));
883
1045
  if (!entry) {
884
1046
  return;
885
1047
  }
@@ -902,6 +1064,219 @@ export class OpenGoatService {
902
1064
  throw new Error(`OpenClaw agent location repair failed creating "${params.agentId}" (exit ${recreated.code}). ${recreated.stderr.trim() || recreated.stdout.trim() || ""}`.trim());
903
1065
  }
904
1066
  }
1067
+ async syncOpenClawAgentExecutionPolicies(paths, rawAgentIds) {
1068
+ if (!this.commandRunner) {
1069
+ return [];
1070
+ }
1071
+ const agentIds = [
1072
+ ...new Set(rawAgentIds
1073
+ .map((agentId) => normalizeAgentId(agentId))
1074
+ .filter((agentId) => Boolean(agentId))),
1075
+ ];
1076
+ if (agentIds.length === 0) {
1077
+ return [];
1078
+ }
1079
+ const warnings = [];
1080
+ const env = await this.resolveOpenClawEnv(paths);
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;
1100
+ }
1101
+ catch (error) {
1102
+ if (!(error instanceof ProviderCommandNotFoundError)) {
1103
+ throw error;
1104
+ }
1105
+ return this.providerService.syncOpenClawAgentExecutionPoliciesViaGateway(paths, agentIds, env);
1106
+ }
1107
+ const indexById = new Map();
1108
+ for (let index = 0; index < entries.length; index += 1) {
1109
+ const entry = entries[index];
1110
+ if (!entry) {
1111
+ continue;
1112
+ }
1113
+ const id = normalizeAgentConfigEntryId(entry);
1114
+ if (!id || indexById.has(id)) {
1115
+ continue;
1116
+ }
1117
+ indexById.set(id, index);
1118
+ }
1119
+ for (const agentId of agentIds) {
1120
+ const index = indexById.get(agentId);
1121
+ if (index === undefined) {
1122
+ warnings.push(`OpenClaw config policy sync skipped for "${agentId}" because no agents.list entry was found.`);
1123
+ continue;
1124
+ }
1125
+ const entry = asRecord(entries[index]);
1126
+ if (readAgentSandboxMode(entry) !== OPENCLAW_AGENT_SANDBOX_MODE) {
1127
+ const sandboxSet = await this.runOpenClaw(["config", "set", `agents.list[${index}].sandbox.mode`, OPENCLAW_AGENT_SANDBOX_MODE], { env });
1128
+ if (sandboxSet.code !== 0) {
1129
+ warnings.push(`OpenClaw sandbox policy sync failed for "${agentId}" (code ${sandboxSet.code}). ${sandboxSet.stderr.trim() || sandboxSet.stdout.trim() || ""}`.trim());
1130
+ }
1131
+ }
1132
+ if (!hasAgentToolsAllowAll(entry)) {
1133
+ const toolsSet = await this.runOpenClaw(["config", "set", `agents.list[${index}].tools.allow`, OPENCLAW_AGENT_TOOLS_ALLOW_ALL_JSON], { env });
1134
+ if (toolsSet.code !== 0) {
1135
+ warnings.push(`OpenClaw tools policy sync failed for "${agentId}" (code ${toolsSet.code}). ${toolsSet.stderr.trim() || toolsSet.stdout.trim() || ""}`.trim());
1136
+ }
1137
+ }
1138
+ }
1139
+ return warnings;
1140
+ }
1141
+ async ensureOpenGoatPluginToolsRegistered(paths) {
1142
+ if (!this.commandRunner) {
1143
+ return [];
1144
+ }
1145
+ const warnings = [];
1146
+ const env = await this.resolveOpenClawEnv(paths);
1147
+ const pluginSourcePath = await this.resolveOpenGoatPluginSourcePath();
1148
+ if (!pluginSourcePath) {
1149
+ warnings.push("OpenClaw OpenGoat plugin source path was not found; OpenGoat tools may be unavailable to agents.");
1150
+ return warnings;
1151
+ }
1152
+ warnings.push(...(await this.configureOpenClawPluginSourcePath(env, pluginSourcePath)));
1153
+ return warnings;
1154
+ }
1155
+ async resolveOpenGoatPluginSourcePath() {
1156
+ const explicit = process.env.OPENGOAT_OPENCLAW_PLUGIN_PATH?.trim();
1157
+ const argvEntry = process.argv[1]?.trim();
1158
+ const argvDir = argvEntry ? dirname(resolvePath(argvEntry)) : undefined;
1159
+ const argvPathCandidates = argvDir
1160
+ ? collectPluginPathCandidatesFromArgvDir(argvDir)
1161
+ : [];
1162
+ const candidates = dedupeStrings([
1163
+ explicit,
1164
+ ...argvPathCandidates,
1165
+ resolvePath(process.cwd(), "packages", "openclaw-plugin"),
1166
+ resolvePath(process.cwd(), "dist", "openclaw-plugin"),
1167
+ resolvePath(process.cwd(), "node_modules", "@opengoat", "openclaw-plugin"),
1168
+ argvDir ? resolvePath(argvDir, "..", "dist", "openclaw-plugin") : undefined,
1169
+ argvDir
1170
+ ? resolvePath(argvDir, "..", "node_modules", "@opengoat", "openclaw-plugin")
1171
+ : undefined,
1172
+ argvDir
1173
+ ? resolvePath(argvDir, "..", "..", "@opengoat", "openclaw-plugin")
1174
+ : undefined,
1175
+ ]);
1176
+ for (const candidate of candidates) {
1177
+ const pluginManifestPath = this.pathPort.join(candidate, "openclaw.plugin.json");
1178
+ if (await this.fileSystem.exists(pluginManifestPath)) {
1179
+ return candidate;
1180
+ }
1181
+ }
1182
+ return undefined;
1183
+ }
1184
+ async configureOpenClawPluginSourcePath(env, pluginSourcePath) {
1185
+ const warnings = [];
1186
+ const currentPathsResult = await this.runOpenClaw(["config", "get", "plugins.load.paths"], { env });
1187
+ const currentPaths = currentPathsResult.code === 0
1188
+ ? readStringArray(parseLooseJson(currentPathsResult.stdout))
1189
+ : [];
1190
+ const mergedPaths = await this.mergePluginLoadPaths(pluginSourcePath, currentPaths);
1191
+ if (!samePathList(currentPaths, mergedPaths)) {
1192
+ const setPaths = await this.runOpenClaw(["config", "set", "plugins.load.paths", JSON.stringify(mergedPaths)], { env });
1193
+ if (setPaths.code !== 0) {
1194
+ warnings.push(`OpenClaw plugin source path update failed (code ${setPaths.code}). ${setPaths.stderr.trim() || setPaths.stdout.trim() || ""}`.trim());
1195
+ }
1196
+ }
1197
+ const pluginIds = [
1198
+ OPENCLAW_OPENGOAT_PLUGIN_ID,
1199
+ OPENCLAW_OPENGOAT_PLUGIN_ROOT_ID,
1200
+ OPENCLAW_OPENGOAT_PLUGIN_LEGACY_PACK_ID,
1201
+ OPENCLAW_OPENGOAT_PLUGIN_FALLBACK_ID,
1202
+ ];
1203
+ const enableFailures = [];
1204
+ let pluginEnabled = false;
1205
+ let enabledPluginId;
1206
+ for (const pluginId of pluginIds) {
1207
+ const enablePlugin = await this.runOpenClaw(["config", "set", `plugins.entries.${pluginId}.enabled`, "true"], { env });
1208
+ if (enablePlugin.code === 0) {
1209
+ pluginEnabled = true;
1210
+ enabledPluginId = pluginId;
1211
+ break;
1212
+ }
1213
+ const message = enablePlugin.stderr.trim() || enablePlugin.stdout.trim() || "";
1214
+ if (isPluginNotFoundMessage(message)) {
1215
+ continue;
1216
+ }
1217
+ enableFailures.push(`OpenClaw plugin enable failed for "${pluginId}" (code ${enablePlugin.code}). ${message}`.trim());
1218
+ }
1219
+ if (!pluginEnabled) {
1220
+ if (enableFailures.length === 0) {
1221
+ warnings.push(`OpenClaw plugin enable failed: no matching plugin id was found (${pluginIds.join(", ")}).`);
1222
+ }
1223
+ else {
1224
+ warnings.push(...enableFailures);
1225
+ }
1226
+ }
1227
+ if (enabledPluginId) {
1228
+ const idsToDisable = pluginIds.filter((pluginId) => pluginId !== enabledPluginId);
1229
+ for (const pluginId of idsToDisable) {
1230
+ const disablePlugin = await this.runOpenClaw(["config", "set", `plugins.entries.${pluginId}.enabled`, "false"], { env });
1231
+ if (disablePlugin.code === 0) {
1232
+ continue;
1233
+ }
1234
+ const message = disablePlugin.stderr.trim() || disablePlugin.stdout.trim() || "";
1235
+ if (isPluginNotFoundMessage(message)) {
1236
+ continue;
1237
+ }
1238
+ warnings.push(`OpenClaw plugin cleanup failed for "${pluginId}" (code ${disablePlugin.code}). ${message}`.trim());
1239
+ }
1240
+ }
1241
+ return warnings;
1242
+ }
1243
+ async mergePluginLoadPaths(pluginSourcePath, currentPaths) {
1244
+ const merged = dedupeStrings([pluginSourcePath, ...currentPaths]);
1245
+ const normalizedPluginSource = resolvePath(pluginSourcePath);
1246
+ const filtered = [];
1247
+ for (const candidate of merged) {
1248
+ const normalizedCandidate = resolvePath(candidate);
1249
+ if (pathMatches(normalizedCandidate, normalizedPluginSource)) {
1250
+ filtered.push(candidate);
1251
+ continue;
1252
+ }
1253
+ const manifestId = await this.readPluginManifestId(candidate);
1254
+ if (manifestId &&
1255
+ isOpenGoatPluginId(manifestId)) {
1256
+ continue;
1257
+ }
1258
+ filtered.push(candidate);
1259
+ }
1260
+ return dedupeStrings(filtered);
1261
+ }
1262
+ async readPluginManifestId(path) {
1263
+ const manifestPath = this.pathPort.join(path, "openclaw.plugin.json");
1264
+ if (!(await this.fileSystem.exists(manifestPath))) {
1265
+ return undefined;
1266
+ }
1267
+ try {
1268
+ const raw = await this.fileSystem.readFile(manifestPath);
1269
+ const parsed = parseLooseJson(raw);
1270
+ const id = asRecord(parsed).id;
1271
+ if (typeof id === "string" && id.trim().length > 0) {
1272
+ return id.trim();
1273
+ }
1274
+ }
1275
+ catch {
1276
+ // Ignore malformed manifests when cleaning up load paths.
1277
+ }
1278
+ return undefined;
1279
+ }
905
1280
  async resolveOpenClawEnv(paths) {
906
1281
  const providerConfig = await this.providerService.getProviderConfig(paths, OPENCLAW_PROVIDER_ID);
907
1282
  return {
@@ -912,6 +1287,7 @@ export class OpenGoatService {
912
1287
  async collectInactiveAgents(paths, manifests, inactiveMinutes, notificationTarget) {
913
1288
  const nowMs = this.resolveNowMs();
914
1289
  const inactiveCutoffMs = nowMs - inactiveMinutes * 60_000;
1290
+ const reporteeStats = buildReporteeStats(manifests);
915
1291
  const inactive = [];
916
1292
  for (const manifest of manifests) {
917
1293
  const managerAgentId = normalizeAgentId(manifest.metadata.reportsTo ?? "");
@@ -926,295 +1302,201 @@ export class OpenGoatService {
926
1302
  if (lastAction && lastAction.timestamp >= inactiveCutoffMs) {
927
1303
  continue;
928
1304
  }
1305
+ const directReporteesCount = reporteeStats.directByManager.get(manifest.agentId) ?? 0;
1306
+ const totalReporteesCount = reporteeStats.totalByManager.get(manifest.agentId) ?? 0;
1307
+ const indirectReporteesCount = Math.max(0, totalReporteesCount - directReporteesCount);
929
1308
  inactive.push({
930
1309
  managerAgentId,
931
1310
  subjectAgentId: manifest.agentId,
932
1311
  subjectName: manifest.metadata.name,
933
1312
  role: manifest.metadata.description,
1313
+ directReporteesCount,
1314
+ indirectReporteesCount,
934
1315
  lastActionTimestamp: lastAction?.timestamp,
935
1316
  });
936
1317
  }
937
1318
  return inactive;
938
1319
  }
939
- async resolveLatestProjectPathForAgent(paths, rawAgentId) {
940
- const agentId = normalizeAgentId(rawAgentId);
941
- if (!agentId) {
942
- return undefined;
943
- }
944
- const sessions = await this.sessionService.listSessions(paths, agentId);
945
- const latestProjectPath = sessions[0]?.projectPath?.trim();
946
- return latestProjectPath || undefined;
947
- }
948
- }
949
- function containsAlreadyExistsMessage(stdout, stderr) {
950
- const text = `${stdout}\n${stderr}`.toLowerCase();
951
- return /\balready exists?\b/.test(text);
952
1320
  }
953
- function containsAgentNotFoundMessage(stdout, stderr) {
954
- const text = `${stdout}\n${stderr}`.toLowerCase();
955
- return /\b(not found|does not exist|no such agent|unknown agent|could not find|no agent found|not exist)\b/.test(text);
956
- }
957
- function toErrorMessage(error) {
958
- if (error instanceof Error) {
959
- return error.message;
1321
+ function asRecord(value) {
1322
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1323
+ return {};
960
1324
  }
961
- return String(error);
1325
+ return value;
962
1326
  }
963
- function resolveInactiveMinutes(value) {
964
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
965
- return 30;
1327
+ function normalizeAgentConfigEntryId(value) {
1328
+ const entry = asRecord(value);
1329
+ const id = entry.id;
1330
+ if (typeof id !== "string") {
1331
+ return undefined;
966
1332
  }
967
- return Math.floor(value);
1333
+ return normalizeAgentId(id);
968
1334
  }
969
- function resolveInactiveAgentNotificationTarget(value) {
970
- return value === "ceo-only" ? "ceo-only" : "all-managers";
1335
+ function readAgentSandboxMode(entry) {
1336
+ const sandbox = asRecord(entry.sandbox);
1337
+ const mode = sandbox.mode;
1338
+ if (typeof mode !== "string") {
1339
+ return undefined;
1340
+ }
1341
+ const trimmed = mode.trim();
1342
+ return trimmed.length > 0 ? trimmed : undefined;
971
1343
  }
972
- function extractManagedSkillsDir(payload) {
973
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
974
- return null;
975
- }
976
- const record = payload;
977
- if (typeof record.managedSkillsDir !== "string") {
978
- return null;
1344
+ function hasAgentToolsAllowAll(entry) {
1345
+ const tools = asRecord(entry.tools);
1346
+ const allow = tools.allow;
1347
+ if (!Array.isArray(allow)) {
1348
+ return false;
979
1349
  }
980
- const managedSkillsDir = record.managedSkillsDir.trim();
981
- return managedSkillsDir || null;
1350
+ return allow.some((value) => typeof value === "string" && value.trim() === "*");
982
1351
  }
983
- function extractOpenClawAgents(payload) {
984
- if (!Array.isArray(payload)) {
1352
+ function readStringArray(value) {
1353
+ if (!Array.isArray(value)) {
985
1354
  return [];
986
1355
  }
987
- const entries = [];
988
- for (const entry of payload) {
989
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
990
- continue;
991
- }
992
- const record = entry;
993
- const id = normalizeAgentId(String(record.id ?? ""));
994
- if (!id) {
995
- continue;
996
- }
997
- entries.push({
998
- id,
999
- workspace: typeof record.workspace === "string" ? record.workspace : "",
1000
- agentDir: typeof record.agentDir === "string" ? record.agentDir : "",
1001
- });
1002
- }
1003
- return entries;
1356
+ return value
1357
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
1358
+ .filter((entry) => entry.length > 0);
1004
1359
  }
1005
- function extractOpenClawAgentEntry(payload, agentId) {
1006
- const normalizedAgentId = normalizeAgentId(agentId);
1007
- if (!normalizedAgentId) {
1008
- return null;
1360
+ async function runWithConcurrency(items, rawConcurrency, worker) {
1361
+ if (items.length === 0) {
1362
+ return [];
1009
1363
  }
1010
- for (const entry of extractOpenClawAgents(payload)) {
1011
- if (entry.id !== normalizedAgentId) {
1012
- continue;
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);
1013
1375
  }
1014
- return {
1015
- workspace: entry.workspace,
1016
- agentDir: entry.agentDir,
1017
- };
1018
- }
1019
- return null;
1020
- }
1021
- function pathMatches(left, right) {
1022
- const leftNormalized = normalizePathForCompare(left);
1023
- const rightNormalized = normalizePathForCompare(right);
1024
- if (!leftNormalized || !rightNormalized) {
1025
- return false;
1026
- }
1027
- return leftNormalized === rightNormalized;
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;
1028
1381
  }
1029
- function pathIsWithin(containerPath, candidatePath) {
1030
- const normalizedContainer = normalizePathForCompare(containerPath);
1031
- const normalizedCandidate = normalizePathForCompare(candidatePath);
1032
- if (!normalizedContainer || !normalizedCandidate) {
1033
- return false;
1034
- }
1035
- const relative = path.relative(normalizedContainer, normalizedCandidate);
1036
- if (!relative) {
1037
- return true;
1382
+ async function runWithConcurrencyByKey(items, rawConcurrency, resolveKey, worker) {
1383
+ if (items.length === 0) {
1384
+ return [];
1038
1385
  }
1039
- return !relative.startsWith("..") && !path.isAbsolute(relative);
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;
1040
1401
  }
1041
- function normalizePathForCompare(value) {
1042
- const trimmed = value.trim();
1402
+ function parseLooseJson(raw) {
1403
+ const trimmed = raw.trim();
1043
1404
  if (!trimmed) {
1044
- return "";
1045
- }
1046
- const resolved = path.resolve(trimmed);
1047
- if (process.platform === "win32") {
1048
- return resolved.toLowerCase();
1405
+ return undefined;
1406
+ }
1407
+ try {
1408
+ return JSON.parse(trimmed);
1409
+ }
1410
+ catch {
1411
+ // continue
1412
+ }
1413
+ const starts = dedupeNumbers([
1414
+ trimmed.indexOf("{"),
1415
+ trimmed.indexOf("["),
1416
+ trimmed.lastIndexOf("{"),
1417
+ trimmed.lastIndexOf("["),
1418
+ ]).filter((index) => index >= 0);
1419
+ for (const startIndex of starts) {
1420
+ const candidate = trimmed.slice(startIndex).trim();
1421
+ if (!candidate) {
1422
+ continue;
1423
+ }
1424
+ try {
1425
+ return JSON.parse(candidate);
1426
+ }
1427
+ catch {
1428
+ // keep trying candidates
1429
+ }
1049
1430
  }
1050
- return resolved;
1051
- }
1052
- function buildTaskSessionRef(agentId, taskId) {
1053
- const normalizedAgentId = normalizeAgentId(agentId) || DEFAULT_AGENT_ID;
1054
- const normalizedTaskId = normalizeAgentId(taskId) || "task";
1055
- return `agent:${normalizedAgentId}:agent_${normalizedAgentId}_task_${normalizedTaskId}`;
1056
- }
1057
- function buildInactiveSessionRef(managerAgentId, subjectAgentId) {
1058
- const manager = normalizeAgentId(managerAgentId) || DEFAULT_AGENT_ID;
1059
- const subject = normalizeAgentId(subjectAgentId) || "agent";
1060
- return `agent:${manager}:agent_${manager}_inactive_${subject}`;
1061
- }
1062
- function buildTodoTaskMessage(params) {
1063
- const blockers = params.task.blockers.length > 0 ? params.task.blockers.join("; ") : "None";
1064
- const artifacts = params.task.artifacts.length > 0
1065
- ? params.task.artifacts
1066
- .map((entry) => `- ${entry.createdAt} @${entry.createdBy}: ${entry.content}`)
1067
- .join("\n")
1068
- : "- None";
1069
- const worklog = params.task.worklog.length > 0
1070
- ? params.task.worklog
1071
- .map((entry) => `- ${entry.createdAt} @${entry.createdBy}: ${entry.content}`)
1072
- .join("\n")
1073
- : "- None";
1074
- return [
1075
- `Task #${params.task.taskId} is assigned to you and currently in TODO. Please work on it now.`,
1076
- "",
1077
- `Task ID: ${params.task.taskId}`,
1078
- `Title: ${params.task.title}`,
1079
- `Description: ${params.task.description}`,
1080
- `Project: ${params.task.project}`,
1081
- `Status: ${params.task.status}`,
1082
- `Owner: @${params.task.owner}`,
1083
- `Assigned to: @${params.task.assignedTo}`,
1084
- `Created at: ${params.task.createdAt}`,
1085
- `Blockers: ${blockers}`,
1086
- "Artifacts:",
1087
- artifacts,
1088
- "Worklog:",
1089
- worklog,
1090
- ].join("\n");
1091
- }
1092
- function buildBlockedTaskMessage(params) {
1093
- const blockerReason = params.task.blockers.length > 0
1094
- ? params.task.blockers.join("; ")
1095
- : params.task.statusReason?.trim() || "no blocker details were provided";
1096
- const artifacts = params.task.artifacts.length > 0
1097
- ? params.task.artifacts
1098
- .map((entry) => `- ${entry.createdAt} @${entry.createdBy}: ${entry.content}`)
1099
- .join("\n")
1100
- : "- None";
1101
- const worklog = params.task.worklog.length > 0
1102
- ? params.task.worklog
1103
- .map((entry) => `- ${entry.createdAt} @${entry.createdBy}: ${entry.content}`)
1104
- .join("\n")
1105
- : "- None";
1106
- return [
1107
- `Task #${params.task.taskId}, assigned to your reportee "@${params.task.assignedTo}" is blocked because of ${blockerReason}. Help unblocking it.`,
1108
- "",
1109
- `Task ID: ${params.task.taskId}`,
1110
- `Title: ${params.task.title}`,
1111
- `Description: ${params.task.description}`,
1112
- `Project: ${params.task.project}`,
1113
- `Status: ${params.task.status}`,
1114
- `Owner: @${params.task.owner}`,
1115
- `Assigned to: @${params.task.assignedTo}`,
1116
- `Created at: ${params.task.createdAt}`,
1117
- "Artifacts:",
1118
- artifacts,
1119
- "Worklog:",
1120
- worklog,
1121
- ].join("\n");
1431
+ return undefined;
1122
1432
  }
1123
- function buildInactiveAgentMessage(params) {
1124
- const lastAction = typeof params.lastActionTimestamp === "number" &&
1125
- Number.isFinite(params.lastActionTimestamp)
1126
- ? new Date(params.lastActionTimestamp).toISOString()
1127
- : null;
1128
- return [
1129
- `Your reportee "@${params.subjectAgentId}" (${params.subjectName}) has no activity in the last ${params.inactiveMinutes} minutes.`,
1130
- ...(params.role ? [`Role: ${params.role}`] : []),
1131
- ...(lastAction ? [`Last action: ${lastAction}`] : []),
1132
- "Please check in and unblock progress.",
1133
- ].join("\n");
1433
+ function dedupeNumbers(values) {
1434
+ return [...new Set(values)];
1134
1435
  }
1135
- function assertAgentExists(manifests, agentId) {
1136
- if (manifests.some((manifest) => manifest.agentId === agentId)) {
1137
- return;
1436
+ function resolveCreateAgentManagerId(agentId, reportsTo) {
1437
+ const normalizedAgentId = normalizeAgentId(agentId);
1438
+ if (isDefaultAgentId(normalizedAgentId)) {
1439
+ return null;
1138
1440
  }
1139
- throw new Error(`Agent "${agentId}" does not exist.`);
1140
- }
1141
- function collectAllReportees(manifests, managerAgentId) {
1142
- const byManager = new Map();
1143
- for (const manifest of manifests) {
1144
- const reportsTo = manifest.metadata.reportsTo;
1145
- if (!reportsTo) {
1146
- continue;
1147
- }
1148
- const reportees = byManager.get(reportsTo) ?? [];
1149
- reportees.push(manifest.agentId);
1150
- byManager.set(reportsTo, reportees);
1441
+ if (reportsTo === null || reportsTo === undefined) {
1442
+ return DEFAULT_AGENT_ID;
1151
1443
  }
1152
- const visited = new Set();
1153
- const queue = [...(byManager.get(managerAgentId) ?? [])];
1154
- while (queue.length > 0) {
1155
- const current = queue.shift();
1156
- if (!current || current === managerAgentId || visited.has(current)) {
1157
- continue;
1158
- }
1159
- visited.add(current);
1160
- queue.push(...(byManager.get(current) ?? []));
1444
+ const normalizedManagerId = normalizeAgentId(reportsTo);
1445
+ if (!normalizedManagerId || normalizedManagerId === normalizedAgentId) {
1446
+ return DEFAULT_AGENT_ID;
1161
1447
  }
1162
- return [...visited].sort((left, right) => left.localeCompare(right));
1448
+ return normalizedManagerId;
1163
1449
  }
1164
- function prepareOpenClawCommandEnv(env) {
1165
- const mergedPath = dedupePathEntries([
1166
- ...resolvePreferredOpenClawCommandPaths(env),
1167
- ...(env.PATH?.split(path.delimiter) ?? []),
1168
- ]);
1169
- return {
1170
- ...env,
1171
- PATH: mergedPath.join(path.delimiter),
1172
- };
1450
+ function collectPluginPathCandidatesFromArgvDir(argvDir) {
1451
+ const maxDepth = 8;
1452
+ const candidates = [];
1453
+ let currentDir = argvDir;
1454
+ for (let depth = 0; depth < maxDepth; depth += 1) {
1455
+ candidates.push(resolvePath(currentDir, "packages", "openclaw-plugin"));
1456
+ candidates.push(resolvePath(currentDir, "openclaw-plugin"));
1457
+ const parentDir = dirname(currentDir);
1458
+ if (parentDir === currentDir) {
1459
+ break;
1460
+ }
1461
+ currentDir = parentDir;
1462
+ }
1463
+ return dedupeStrings(candidates);
1173
1464
  }
1174
- function resolvePreferredOpenClawCommandPaths(env) {
1175
- const homeDir = homedir();
1176
- const preferredPaths = [
1177
- path.dirname(process.execPath),
1178
- path.join(homeDir, ".npm-global", "bin"),
1179
- path.join(homeDir, ".npm", "bin"),
1180
- path.join(homeDir, ".local", "bin"),
1181
- path.join(homeDir, ".volta", "bin"),
1182
- path.join(homeDir, ".fnm", "current", "bin"),
1183
- path.join(homeDir, ".asdf", "shims"),
1184
- path.join(homeDir, "bin"),
1185
- ];
1186
- const npmPrefixCandidates = dedupePathEntries([
1187
- env.npm_config_prefix ?? "",
1188
- env.NPM_CONFIG_PREFIX ?? "",
1189
- process.env.npm_config_prefix ?? "",
1190
- process.env.NPM_CONFIG_PREFIX ?? "",
1191
- ]);
1192
- for (const prefix of npmPrefixCandidates) {
1193
- preferredPaths.push(path.join(prefix, "bin"));
1194
- }
1195
- if (process.platform === "darwin") {
1196
- preferredPaths.push("/opt/homebrew/bin", "/opt/homebrew/opt/node@22/bin", "/usr/local/opt/node@22/bin");
1197
- }
1198
- return preferredPaths;
1199
- }
1200
- function dedupePathEntries(entries) {
1465
+ function dedupeStrings(values) {
1201
1466
  const seen = new Set();
1202
1467
  const deduped = [];
1203
- for (const rawEntry of entries) {
1204
- const entry = rawEntry.trim();
1205
- if (!entry || seen.has(entry)) {
1468
+ for (const value of values) {
1469
+ const trimmed = value?.trim();
1470
+ if (!trimmed || seen.has(trimmed)) {
1206
1471
  continue;
1207
1472
  }
1208
- seen.add(entry);
1209
- deduped.push(entry);
1473
+ seen.add(trimmed);
1474
+ deduped.push(trimmed);
1210
1475
  }
1211
1476
  return deduped;
1212
1477
  }
1213
- function isSpawnPermissionOrMissing(error) {
1214
- return (typeof error === "object" &&
1215
- error !== null &&
1216
- "code" in error &&
1217
- ((error.code ?? "") === "ENOENT" ||
1218
- (error.code ?? "") === "EACCES"));
1478
+ function samePathList(left, right) {
1479
+ if (left.length !== right.length) {
1480
+ return false;
1481
+ }
1482
+ for (let index = 0; index < left.length; index += 1) {
1483
+ const leftValue = left[index];
1484
+ const rightValue = right[index];
1485
+ if (!leftValue || !rightValue || !pathMatches(leftValue, rightValue)) {
1486
+ return false;
1487
+ }
1488
+ }
1489
+ return true;
1490
+ }
1491
+ function isOpenGoatPluginId(pluginId) {
1492
+ return [
1493
+ OPENCLAW_OPENGOAT_PLUGIN_ID,
1494
+ OPENCLAW_OPENGOAT_PLUGIN_ROOT_ID,
1495
+ OPENCLAW_OPENGOAT_PLUGIN_LEGACY_PACK_ID,
1496
+ OPENCLAW_OPENGOAT_PLUGIN_FALLBACK_ID,
1497
+ ].includes(pluginId.trim().toLowerCase());
1498
+ }
1499
+ function isPluginNotFoundMessage(message) {
1500
+ return message.toLowerCase().includes("plugin not found");
1219
1501
  }
1220
1502
  //# sourceMappingURL=opengoat.service.js.map