@opengoat/core 2026.2.14 → 2026.2.15

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 (24) hide show
  1. package/dist/core/agents/application/agent.service.d.ts +7 -0
  2. package/dist/core/agents/application/agent.service.js +83 -8
  3. package/dist/core/agents/application/agent.service.js.map +1 -1
  4. package/dist/core/bootstrap/application/bootstrap.service.js +14 -1
  5. package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -1
  6. package/dist/core/opengoat/application/opengoat.service.d.ts +9 -0
  7. package/dist/core/opengoat/application/opengoat.service.helpers.d.ts +52 -0
  8. package/dist/core/opengoat/application/opengoat.service.helpers.js +325 -0
  9. package/dist/core/opengoat/application/opengoat.service.helpers.js.map +1 -0
  10. package/dist/core/opengoat/application/opengoat.service.js +450 -317
  11. package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
  12. package/dist/core/orchestration/application/orchestration.service.d.ts +1 -0
  13. package/dist/core/orchestration/application/orchestration.service.js +109 -24
  14. package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
  15. package/dist/core/providers/application/provider.service.d.ts +1 -0
  16. package/dist/core/providers/application/provider.service.js +30 -20
  17. package/dist/core/providers/application/provider.service.js.map +1 -1
  18. package/dist/core/templates/assets/ceo/BOOTSTRAP.md +3 -3
  19. package/dist/core/templates/assets/ceo/ROLE.md +2 -1
  20. package/dist/core/templates/assets/skills/og-board-individual/SKILL.md +66 -89
  21. package/dist/core/templates/assets/skills/og-board-manager/SKILL.md +64 -45
  22. package/dist/core/templates/default-templates.js +15 -3
  23. package/dist/core/templates/default-templates.js.map +1 -1
  24. package/package.json +1 -1
@@ -1,5 +1,3 @@
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";
@@ -9,10 +7,18 @@ 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, buildInactiveSessionRef, buildReporteeStats, buildTaskSessionRef, buildTodoTaskMessage, collectAllReportees, containsAgentNotFoundMessage, containsAlreadyExistsMessage, extractManagedSkillsDir, extractOpenClawAgents, isSpawnPermissionOrMissing, pathIsWithin, pathMatches, prepareOpenClawCommandEnv, resolveInactiveAgentNotificationTarget, resolveInactiveMinutes, 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)}`);
229
228
  }
230
229
  try {
231
- await this.agentService.ensureCeoWorkspaceBootstrap(paths);
230
+ warnings.push(...(await this.syncOpenClawAgentExecutionPolicies(paths, localAgents.map((agent) => agent.id))));
232
231
  }
233
232
  catch (error) {
234
- warnings.push(`OpenGoat workspace bootstrap for "ceo" failed: ${toErrorMessage(error)}`);
233
+ warnings.push(`OpenClaw agent policy sync failed: ${toErrorMessage(error)}`);
234
+ }
235
+ try {
236
+ warnings.push(...(await this.ensureOpenGoatPluginToolsRegistered(paths)));
237
+ }
238
+ catch (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,
@@ -287,6 +301,7 @@ export class OpenGoatService {
287
301
  }
288
302
  throw new Error(`OpenClaw agent location sync failed for "${created.agent.id}". ${toErrorMessage(error)}`);
289
303
  }
304
+ await this.syncOpenClawAgentExecutionPolicies(paths, [created.agent.id]);
290
305
  try {
291
306
  const workspaceBootstrap = await this.agentService.ensureAgentWorkspaceBootstrap(paths, {
292
307
  agentId: created.agent.id,
@@ -390,12 +405,13 @@ export class OpenGoatService {
390
405
  this.agentService.listAgents(paths),
391
406
  this.agentManifestService.listManifests(paths),
392
407
  ]);
408
+ const reporteeStats = buildReporteeStats(manifests);
393
409
  const descriptorsById = new Map(agents.map((agent) => [agent.id, agent]));
394
410
  const agent = descriptorsById.get(agentId);
395
411
  if (!agent) {
396
412
  throw new Error(`Agent "${agentId}" does not exist.`);
397
413
  }
398
- const totalReportees = collectAllReportees(manifests, agentId).length;
414
+ const totalReportees = reporteeStats.totalByManager.get(agentId) ?? 0;
399
415
  const directReportees = manifests
400
416
  .filter((manifest) => manifest.metadata.reportsTo === agentId)
401
417
  .map((manifest) => {
@@ -408,8 +424,7 @@ export class OpenGoatService {
408
424
  id: manifest.agentId,
409
425
  name,
410
426
  role,
411
- totalReportees: collectAllReportees(manifests, manifest.agentId)
412
- .length,
427
+ totalReportees: reporteeStats.totalByManager.get(manifest.agentId) ?? 0,
413
428
  };
414
429
  })
415
430
  .sort((left, right) => left.id.localeCompare(right.id));
@@ -507,7 +522,6 @@ export class OpenGoatService {
507
522
  const paths = this.pathsProvider.getPaths();
508
523
  const ranAt = this.resolveNowIso();
509
524
  const manifests = await this.agentManifestService.listManifests(paths);
510
- const manifestsById = new Map(manifests.map((manifest) => [manifest.agentId, manifest]));
511
525
  const inactiveMinutes = resolveInactiveMinutes(options.inactiveMinutes);
512
526
  const notificationTarget = resolveInactiveAgentNotificationTarget(options.notificationTarget);
513
527
  const notifyInactiveAgents = options.notifyInactiveAgents ?? true;
@@ -518,8 +532,27 @@ export class OpenGoatService {
518
532
  ? await this.resolveLatestProjectPathForAgent(paths, DEFAULT_AGENT_ID)
519
533
  : undefined;
520
534
  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);
537
+ const dispatches = [
538
+ ...taskStatusDispatch.dispatches,
539
+ ...inactiveDispatches,
540
+ ];
541
+ const failed = dispatches.filter((entry) => !entry.ok).length;
542
+ return {
543
+ ranAt,
544
+ scannedTasks: tasks.length,
545
+ todoTasks: taskStatusDispatch.todoTasks,
546
+ blockedTasks: taskStatusDispatch.blockedTasks,
547
+ inactiveAgents: inactiveCandidates.length,
548
+ sent: dispatches.length - failed,
549
+ failed,
550
+ dispatches,
551
+ };
552
+ }
553
+ async dispatchTaskStatusAutomations(paths, tasks, manifests) {
554
+ const manifestsById = new Map(manifests.map((manifest) => [manifest.agentId, manifest]));
521
555
  const dispatches = [];
522
- let scannedTasks = tasks.length;
523
556
  let todoTasks = 0;
524
557
  let blockedTasks = 0;
525
558
  for (const task of tasks) {
@@ -558,6 +591,14 @@ export class OpenGoatService {
558
591
  error: result.error,
559
592
  });
560
593
  }
594
+ return {
595
+ dispatches,
596
+ todoTasks,
597
+ blockedTasks,
598
+ };
599
+ }
600
+ async dispatchInactiveAgentAutomations(paths, inactiveCandidates, inactiveMinutes, latestCeoProjectPath) {
601
+ const dispatches = [];
561
602
  for (const candidate of inactiveCandidates) {
562
603
  const sessionRef = buildInactiveSessionRef(candidate.managerAgentId, candidate.subjectAgentId);
563
604
  const message = buildInactiveAgentMessage({
@@ -565,6 +606,8 @@ export class OpenGoatService {
565
606
  subjectAgentId: candidate.subjectAgentId,
566
607
  subjectName: candidate.subjectName,
567
608
  role: candidate.role,
609
+ directReporteesCount: candidate.directReporteesCount,
610
+ indirectReporteesCount: candidate.indirectReporteesCount,
568
611
  inactiveMinutes,
569
612
  lastActionTimestamp: candidate.lastActionTimestamp,
570
613
  });
@@ -580,17 +623,7 @@ export class OpenGoatService {
580
623
  error: result.error,
581
624
  });
582
625
  }
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
- };
626
+ return dispatches;
594
627
  }
595
628
  async listSkills(agentId = DEFAULT_AGENT_ID) {
596
629
  const paths = this.pathsProvider.getPaths();
@@ -689,6 +722,7 @@ export class OpenGoatService {
689
722
  const result = await this.orchestrationService.runAgent(paths, agentId, {
690
723
  message,
691
724
  sessionRef,
725
+ disableSession: options.disableSession ?? true,
692
726
  cwd: options.cwd,
693
727
  env: process.env,
694
728
  });
@@ -815,11 +849,8 @@ export class OpenGoatService {
815
849
  if (skillsList.code !== 0) {
816
850
  throw new Error(`OpenClaw skills list failed (exit ${skillsList.code}). ${skillsList.stderr.trim() || skillsList.stdout.trim() || ""}`.trim());
817
851
  }
818
- let parsed;
819
- try {
820
- parsed = JSON.parse(skillsList.stdout);
821
- }
822
- catch {
852
+ const parsed = parseLooseJson(skillsList.stdout);
853
+ if (parsed === undefined) {
823
854
  throw new Error("OpenClaw skills list returned non-JSON output; cannot resolve managed skills directory.");
824
855
  }
825
856
  const managedSkillsDir = extractManagedSkillsDir(parsed);
@@ -837,11 +868,8 @@ export class OpenGoatService {
837
868
  if (listed.code !== 0) {
838
869
  throw new Error(`OpenClaw agents list failed (exit ${listed.code}). ${listed.stderr.trim() || listed.stdout.trim() || ""}`.trim());
839
870
  }
840
- let parsed;
841
- try {
842
- parsed = JSON.parse(listed.stdout);
843
- }
844
- catch {
871
+ const parsed = parseLooseJson(listed.stdout);
872
+ if (parsed === undefined) {
845
873
  throw new Error("OpenClaw agents list returned non-JSON output; cannot inspect agents.");
846
874
  }
847
875
  return extractOpenClawAgents(parsed);
@@ -861,25 +889,66 @@ export class OpenGoatService {
861
889
  warnings.push(`Workspace fallback discovery failed: ${toErrorMessage(error)}`);
862
890
  }
863
891
  }
864
- async ensureOpenClawAgentLocation(paths, params) {
865
- if (!this.commandRunner) {
866
- return;
892
+ async syncOpenClawAgentRegistration(paths, params) {
893
+ const warnings = [];
894
+ if (params.existingEntry &&
895
+ pathMatches(params.existingEntry.workspace, params.descriptor.workspaceDir) &&
896
+ pathMatches(params.existingEntry.agentDir, params.descriptor.internalConfigDir)) {
897
+ return {
898
+ synced: true,
899
+ code: 0,
900
+ warnings,
901
+ };
867
902
  }
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());
903
+ let runtimeSync;
904
+ try {
905
+ runtimeSync = await this.providerService.createProviderAgent(paths, params.descriptor.id, {
906
+ providerId: OPENCLAW_PROVIDER_ID,
907
+ displayName: params.descriptor.displayName,
908
+ workspaceDir: params.descriptor.workspaceDir,
909
+ internalConfigDir: params.descriptor.internalConfigDir,
910
+ });
911
+ }
912
+ catch (error) {
913
+ warnings.push(`OpenClaw sync for "${params.descriptor.id}" failed: ${toErrorMessage(error)}`);
914
+ return {
915
+ synced: false,
916
+ warnings,
917
+ };
918
+ }
919
+ const synced = runtimeSync.code === 0 ||
920
+ containsAlreadyExistsMessage(runtimeSync.stdout, runtimeSync.stderr);
921
+ if (!synced) {
922
+ warnings.push(`OpenClaw sync for "${params.descriptor.id}" failed (code ${runtimeSync.code}). ${(runtimeSync.stderr || runtimeSync.stdout).trim()}`);
923
+ return {
924
+ synced,
925
+ code: runtimeSync.code,
926
+ warnings,
927
+ };
874
928
  }
875
- let parsed;
876
929
  try {
877
- parsed = JSON.parse(listed.stdout);
930
+ await this.ensureOpenClawAgentLocation(paths, {
931
+ agentId: params.descriptor.id,
932
+ displayName: params.descriptor.displayName,
933
+ workspaceDir: params.descriptor.workspaceDir,
934
+ internalConfigDir: params.descriptor.internalConfigDir,
935
+ }, params.existingEntry);
878
936
  }
879
- catch {
880
- throw new Error("OpenClaw agents list returned non-JSON output; cannot verify agent location.");
937
+ catch (error) {
938
+ warnings.push(`OpenClaw location sync for "${params.descriptor.id}" failed: ${toErrorMessage(error)}`);
939
+ }
940
+ return {
941
+ synced,
942
+ code: runtimeSync.code,
943
+ warnings,
944
+ };
945
+ }
946
+ async ensureOpenClawAgentLocation(paths, params, existingEntry) {
947
+ if (!this.commandRunner) {
948
+ return;
881
949
  }
882
- const entry = extractOpenClawAgentEntry(parsed, params.agentId);
950
+ const entry = existingEntry ??
951
+ (await this.listOpenClawAgents(paths)).find((candidate) => candidate.id === normalizeAgentId(params.agentId));
883
952
  if (!entry) {
884
953
  return;
885
954
  }
@@ -902,6 +971,210 @@ export class OpenGoatService {
902
971
  throw new Error(`OpenClaw agent location repair failed creating "${params.agentId}" (exit ${recreated.code}). ${recreated.stderr.trim() || recreated.stdout.trim() || ""}`.trim());
903
972
  }
904
973
  }
974
+ async syncOpenClawAgentExecutionPolicies(paths, rawAgentIds) {
975
+ if (!this.commandRunner) {
976
+ return [];
977
+ }
978
+ const agentIds = [
979
+ ...new Set(rawAgentIds
980
+ .map((agentId) => normalizeAgentId(agentId))
981
+ .filter((agentId) => Boolean(agentId))),
982
+ ];
983
+ if (agentIds.length === 0) {
984
+ return [];
985
+ }
986
+ const warnings = [];
987
+ 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;
999
+ }
1000
+ if (!Array.isArray(parsed)) {
1001
+ warnings.push("OpenClaw config agents.list is not an array; skipping sandbox/tools policy sync.");
1002
+ return warnings;
1003
+ }
1004
+ const entries = parsed;
1005
+ const indexById = new Map();
1006
+ for (let index = 0; index < entries.length; index += 1) {
1007
+ const entry = entries[index];
1008
+ if (!entry) {
1009
+ continue;
1010
+ }
1011
+ const id = normalizeAgentConfigEntryId(entry);
1012
+ if (!id || indexById.has(id)) {
1013
+ continue;
1014
+ }
1015
+ indexById.set(id, index);
1016
+ }
1017
+ for (const agentId of agentIds) {
1018
+ const index = indexById.get(agentId);
1019
+ if (index === undefined) {
1020
+ warnings.push(`OpenClaw config policy sync skipped for "${agentId}" because no agents.list entry was found.`);
1021
+ continue;
1022
+ }
1023
+ const entry = asRecord(entries[index]);
1024
+ if (readAgentSandboxMode(entry) !== OPENCLAW_AGENT_SANDBOX_MODE) {
1025
+ const sandboxSet = await this.runOpenClaw(["config", "set", `agents.list[${index}].sandbox.mode`, OPENCLAW_AGENT_SANDBOX_MODE], { env });
1026
+ if (sandboxSet.code !== 0) {
1027
+ warnings.push(`OpenClaw sandbox policy sync failed for "${agentId}" (code ${sandboxSet.code}). ${sandboxSet.stderr.trim() || sandboxSet.stdout.trim() || ""}`.trim());
1028
+ }
1029
+ }
1030
+ if (!hasAgentToolsAllowAll(entry)) {
1031
+ const toolsSet = await this.runOpenClaw(["config", "set", `agents.list[${index}].tools.allow`, OPENCLAW_AGENT_TOOLS_ALLOW_ALL_JSON], { env });
1032
+ if (toolsSet.code !== 0) {
1033
+ warnings.push(`OpenClaw tools policy sync failed for "${agentId}" (code ${toolsSet.code}). ${toolsSet.stderr.trim() || toolsSet.stdout.trim() || ""}`.trim());
1034
+ }
1035
+ }
1036
+ }
1037
+ return warnings;
1038
+ }
1039
+ async ensureOpenGoatPluginToolsRegistered(paths) {
1040
+ if (!this.commandRunner) {
1041
+ return [];
1042
+ }
1043
+ const warnings = [];
1044
+ const env = await this.resolveOpenClawEnv(paths);
1045
+ const pluginSourcePath = await this.resolveOpenGoatPluginSourcePath();
1046
+ if (!pluginSourcePath) {
1047
+ warnings.push("OpenClaw OpenGoat plugin source path was not found; OpenGoat tools may be unavailable to agents.");
1048
+ return warnings;
1049
+ }
1050
+ warnings.push(...(await this.configureOpenClawPluginSourcePath(env, pluginSourcePath)));
1051
+ return warnings;
1052
+ }
1053
+ async resolveOpenGoatPluginSourcePath() {
1054
+ const explicit = process.env.OPENGOAT_OPENCLAW_PLUGIN_PATH?.trim();
1055
+ const argvEntry = process.argv[1]?.trim();
1056
+ const argvDir = argvEntry ? dirname(resolvePath(argvEntry)) : undefined;
1057
+ const argvPathCandidates = argvDir
1058
+ ? collectPluginPathCandidatesFromArgvDir(argvDir)
1059
+ : [];
1060
+ const candidates = dedupeStrings([
1061
+ explicit,
1062
+ ...argvPathCandidates,
1063
+ resolvePath(process.cwd(), "packages", "openclaw-plugin"),
1064
+ resolvePath(process.cwd(), "dist", "openclaw-plugin"),
1065
+ resolvePath(process.cwd(), "node_modules", "@opengoat", "openclaw-plugin"),
1066
+ argvDir ? resolvePath(argvDir, "..", "dist", "openclaw-plugin") : undefined,
1067
+ argvDir
1068
+ ? resolvePath(argvDir, "..", "node_modules", "@opengoat", "openclaw-plugin")
1069
+ : undefined,
1070
+ argvDir
1071
+ ? resolvePath(argvDir, "..", "..", "@opengoat", "openclaw-plugin")
1072
+ : undefined,
1073
+ ]);
1074
+ for (const candidate of candidates) {
1075
+ const pluginManifestPath = this.pathPort.join(candidate, "openclaw.plugin.json");
1076
+ if (await this.fileSystem.exists(pluginManifestPath)) {
1077
+ return candidate;
1078
+ }
1079
+ }
1080
+ return undefined;
1081
+ }
1082
+ async configureOpenClawPluginSourcePath(env, pluginSourcePath) {
1083
+ const warnings = [];
1084
+ const currentPathsResult = await this.runOpenClaw(["config", "get", "plugins.load.paths"], { env });
1085
+ const currentPaths = currentPathsResult.code === 0
1086
+ ? readStringArray(parseLooseJson(currentPathsResult.stdout))
1087
+ : [];
1088
+ const mergedPaths = await this.mergePluginLoadPaths(pluginSourcePath, currentPaths);
1089
+ if (!samePathList(currentPaths, mergedPaths)) {
1090
+ const setPaths = await this.runOpenClaw(["config", "set", "plugins.load.paths", JSON.stringify(mergedPaths)], { env });
1091
+ if (setPaths.code !== 0) {
1092
+ warnings.push(`OpenClaw plugin source path update failed (code ${setPaths.code}). ${setPaths.stderr.trim() || setPaths.stdout.trim() || ""}`.trim());
1093
+ }
1094
+ }
1095
+ const pluginIds = [
1096
+ OPENCLAW_OPENGOAT_PLUGIN_ID,
1097
+ OPENCLAW_OPENGOAT_PLUGIN_ROOT_ID,
1098
+ OPENCLAW_OPENGOAT_PLUGIN_LEGACY_PACK_ID,
1099
+ OPENCLAW_OPENGOAT_PLUGIN_FALLBACK_ID,
1100
+ ];
1101
+ const enableFailures = [];
1102
+ let pluginEnabled = false;
1103
+ let enabledPluginId;
1104
+ for (const pluginId of pluginIds) {
1105
+ const enablePlugin = await this.runOpenClaw(["config", "set", `plugins.entries.${pluginId}.enabled`, "true"], { env });
1106
+ if (enablePlugin.code === 0) {
1107
+ pluginEnabled = true;
1108
+ enabledPluginId = pluginId;
1109
+ break;
1110
+ }
1111
+ const message = enablePlugin.stderr.trim() || enablePlugin.stdout.trim() || "";
1112
+ if (isPluginNotFoundMessage(message)) {
1113
+ continue;
1114
+ }
1115
+ enableFailures.push(`OpenClaw plugin enable failed for "${pluginId}" (code ${enablePlugin.code}). ${message}`.trim());
1116
+ }
1117
+ if (!pluginEnabled) {
1118
+ if (enableFailures.length === 0) {
1119
+ warnings.push(`OpenClaw plugin enable failed: no matching plugin id was found (${pluginIds.join(", ")}).`);
1120
+ }
1121
+ else {
1122
+ warnings.push(...enableFailures);
1123
+ }
1124
+ }
1125
+ if (enabledPluginId) {
1126
+ const idsToDisable = pluginIds.filter((pluginId) => pluginId !== enabledPluginId);
1127
+ for (const pluginId of idsToDisable) {
1128
+ const disablePlugin = await this.runOpenClaw(["config", "set", `plugins.entries.${pluginId}.enabled`, "false"], { env });
1129
+ if (disablePlugin.code === 0) {
1130
+ continue;
1131
+ }
1132
+ const message = disablePlugin.stderr.trim() || disablePlugin.stdout.trim() || "";
1133
+ if (isPluginNotFoundMessage(message)) {
1134
+ continue;
1135
+ }
1136
+ warnings.push(`OpenClaw plugin cleanup failed for "${pluginId}" (code ${disablePlugin.code}). ${message}`.trim());
1137
+ }
1138
+ }
1139
+ return warnings;
1140
+ }
1141
+ async mergePluginLoadPaths(pluginSourcePath, currentPaths) {
1142
+ const merged = dedupeStrings([pluginSourcePath, ...currentPaths]);
1143
+ const normalizedPluginSource = resolvePath(pluginSourcePath);
1144
+ const filtered = [];
1145
+ for (const candidate of merged) {
1146
+ const normalizedCandidate = resolvePath(candidate);
1147
+ if (pathMatches(normalizedCandidate, normalizedPluginSource)) {
1148
+ filtered.push(candidate);
1149
+ continue;
1150
+ }
1151
+ const manifestId = await this.readPluginManifestId(candidate);
1152
+ if (manifestId &&
1153
+ isOpenGoatPluginId(manifestId)) {
1154
+ continue;
1155
+ }
1156
+ filtered.push(candidate);
1157
+ }
1158
+ return dedupeStrings(filtered);
1159
+ }
1160
+ async readPluginManifestId(path) {
1161
+ const manifestPath = this.pathPort.join(path, "openclaw.plugin.json");
1162
+ if (!(await this.fileSystem.exists(manifestPath))) {
1163
+ return undefined;
1164
+ }
1165
+ try {
1166
+ const raw = await this.fileSystem.readFile(manifestPath);
1167
+ const parsed = parseLooseJson(raw);
1168
+ const id = asRecord(parsed).id;
1169
+ if (typeof id === "string" && id.trim().length > 0) {
1170
+ return id.trim();
1171
+ }
1172
+ }
1173
+ catch {
1174
+ // Ignore malformed manifests when cleaning up load paths.
1175
+ }
1176
+ return undefined;
1177
+ }
905
1178
  async resolveOpenClawEnv(paths) {
906
1179
  const providerConfig = await this.providerService.getProviderConfig(paths, OPENCLAW_PROVIDER_ID);
907
1180
  return {
@@ -912,6 +1185,7 @@ export class OpenGoatService {
912
1185
  async collectInactiveAgents(paths, manifests, inactiveMinutes, notificationTarget) {
913
1186
  const nowMs = this.resolveNowMs();
914
1187
  const inactiveCutoffMs = nowMs - inactiveMinutes * 60_000;
1188
+ const reporteeStats = buildReporteeStats(manifests);
915
1189
  const inactive = [];
916
1190
  for (const manifest of manifests) {
917
1191
  const managerAgentId = normalizeAgentId(manifest.metadata.reportsTo ?? "");
@@ -926,11 +1200,16 @@ export class OpenGoatService {
926
1200
  if (lastAction && lastAction.timestamp >= inactiveCutoffMs) {
927
1201
  continue;
928
1202
  }
1203
+ const directReporteesCount = reporteeStats.directByManager.get(manifest.agentId) ?? 0;
1204
+ const totalReporteesCount = reporteeStats.totalByManager.get(manifest.agentId) ?? 0;
1205
+ const indirectReporteesCount = Math.max(0, totalReporteesCount - directReporteesCount);
929
1206
  inactive.push({
930
1207
  managerAgentId,
931
1208
  subjectAgentId: manifest.agentId,
932
1209
  subjectName: manifest.metadata.name,
933
1210
  role: manifest.metadata.description,
1211
+ directReporteesCount,
1212
+ indirectReporteesCount,
934
1213
  lastActionTimestamp: lastAction?.timestamp,
935
1214
  });
936
1215
  }
@@ -946,275 +1225,129 @@ export class OpenGoatService {
946
1225
  return latestProjectPath || undefined;
947
1226
  }
948
1227
  }
949
- function containsAlreadyExistsMessage(stdout, stderr) {
950
- const text = `${stdout}\n${stderr}`.toLowerCase();
951
- return /\balready exists?\b/.test(text);
952
- }
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;
1228
+ function asRecord(value) {
1229
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1230
+ return {};
960
1231
  }
961
- return String(error);
1232
+ return value;
962
1233
  }
963
- function resolveInactiveMinutes(value) {
964
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
965
- return 30;
1234
+ function normalizeAgentConfigEntryId(value) {
1235
+ const entry = asRecord(value);
1236
+ const id = entry.id;
1237
+ if (typeof id !== "string") {
1238
+ return undefined;
966
1239
  }
967
- return Math.floor(value);
1240
+ return normalizeAgentId(id);
968
1241
  }
969
- function resolveInactiveAgentNotificationTarget(value) {
970
- return value === "ceo-only" ? "ceo-only" : "all-managers";
1242
+ function readAgentSandboxMode(entry) {
1243
+ const sandbox = asRecord(entry.sandbox);
1244
+ const mode = sandbox.mode;
1245
+ if (typeof mode !== "string") {
1246
+ return undefined;
1247
+ }
1248
+ const trimmed = mode.trim();
1249
+ return trimmed.length > 0 ? trimmed : undefined;
971
1250
  }
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;
979
- }
980
- const managedSkillsDir = record.managedSkillsDir.trim();
981
- return managedSkillsDir || null;
982
- }
983
- function extractOpenClawAgents(payload) {
984
- if (!Array.isArray(payload)) {
985
- return [];
986
- }
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;
1004
- }
1005
- function extractOpenClawAgentEntry(payload, agentId) {
1006
- const normalizedAgentId = normalizeAgentId(agentId);
1007
- if (!normalizedAgentId) {
1008
- return null;
1009
- }
1010
- for (const entry of extractOpenClawAgents(payload)) {
1011
- if (entry.id !== normalizedAgentId) {
1012
- continue;
1013
- }
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) {
1251
+ function hasAgentToolsAllowAll(entry) {
1252
+ const tools = asRecord(entry.tools);
1253
+ const allow = tools.allow;
1254
+ if (!Array.isArray(allow)) {
1025
1255
  return false;
1026
1256
  }
1027
- return leftNormalized === rightNormalized;
1257
+ return allow.some((value) => typeof value === "string" && value.trim() === "*");
1028
1258
  }
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;
1259
+ function readStringArray(value) {
1260
+ if (!Array.isArray(value)) {
1261
+ return [];
1038
1262
  }
1039
- return !relative.startsWith("..") && !path.isAbsolute(relative);
1263
+ return value
1264
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
1265
+ .filter((entry) => entry.length > 0);
1040
1266
  }
1041
- function normalizePathForCompare(value) {
1042
- const trimmed = value.trim();
1267
+ function parseLooseJson(raw) {
1268
+ const trimmed = raw.trim();
1043
1269
  if (!trimmed) {
1044
- return "";
1045
- }
1046
- const resolved = path.resolve(trimmed);
1047
- if (process.platform === "win32") {
1048
- return resolved.toLowerCase();
1049
- }
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");
1122
- }
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");
1134
- }
1135
- function assertAgentExists(manifests, agentId) {
1136
- if (manifests.some((manifest) => manifest.agentId === agentId)) {
1137
- return;
1138
- }
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) {
1270
+ return undefined;
1271
+ }
1272
+ try {
1273
+ return JSON.parse(trimmed);
1274
+ }
1275
+ catch {
1276
+ // continue
1277
+ }
1278
+ const starts = dedupeNumbers([
1279
+ trimmed.indexOf("{"),
1280
+ trimmed.indexOf("["),
1281
+ trimmed.lastIndexOf("{"),
1282
+ trimmed.lastIndexOf("["),
1283
+ ]).filter((index) => index >= 0);
1284
+ for (const startIndex of starts) {
1285
+ const candidate = trimmed.slice(startIndex).trim();
1286
+ if (!candidate) {
1146
1287
  continue;
1147
1288
  }
1148
- const reportees = byManager.get(reportsTo) ?? [];
1149
- reportees.push(manifest.agentId);
1150
- byManager.set(reportsTo, reportees);
1151
- }
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;
1289
+ try {
1290
+ return JSON.parse(candidate);
1291
+ }
1292
+ catch {
1293
+ // keep trying candidates
1158
1294
  }
1159
- visited.add(current);
1160
- queue.push(...(byManager.get(current) ?? []));
1161
1295
  }
1162
- return [...visited].sort((left, right) => left.localeCompare(right));
1296
+ return undefined;
1163
1297
  }
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
- };
1298
+ function dedupeNumbers(values) {
1299
+ return [...new Set(values)];
1173
1300
  }
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;
1301
+ function collectPluginPathCandidatesFromArgvDir(argvDir) {
1302
+ const maxDepth = 8;
1303
+ const candidates = [];
1304
+ let currentDir = argvDir;
1305
+ for (let depth = 0; depth < maxDepth; depth += 1) {
1306
+ candidates.push(resolvePath(currentDir, "packages", "openclaw-plugin"));
1307
+ candidates.push(resolvePath(currentDir, "openclaw-plugin"));
1308
+ const parentDir = dirname(currentDir);
1309
+ if (parentDir === currentDir) {
1310
+ break;
1311
+ }
1312
+ currentDir = parentDir;
1313
+ }
1314
+ return dedupeStrings(candidates);
1199
1315
  }
1200
- function dedupePathEntries(entries) {
1316
+ function dedupeStrings(values) {
1201
1317
  const seen = new Set();
1202
1318
  const deduped = [];
1203
- for (const rawEntry of entries) {
1204
- const entry = rawEntry.trim();
1205
- if (!entry || seen.has(entry)) {
1319
+ for (const value of values) {
1320
+ const trimmed = value?.trim();
1321
+ if (!trimmed || seen.has(trimmed)) {
1206
1322
  continue;
1207
1323
  }
1208
- seen.add(entry);
1209
- deduped.push(entry);
1324
+ seen.add(trimmed);
1325
+ deduped.push(trimmed);
1210
1326
  }
1211
1327
  return deduped;
1212
1328
  }
1213
- function isSpawnPermissionOrMissing(error) {
1214
- return (typeof error === "object" &&
1215
- error !== null &&
1216
- "code" in error &&
1217
- ((error.code ?? "") === "ENOENT" ||
1218
- (error.code ?? "") === "EACCES"));
1329
+ function samePathList(left, right) {
1330
+ if (left.length !== right.length) {
1331
+ return false;
1332
+ }
1333
+ for (let index = 0; index < left.length; index += 1) {
1334
+ const leftValue = left[index];
1335
+ const rightValue = right[index];
1336
+ if (!leftValue || !rightValue || !pathMatches(leftValue, rightValue)) {
1337
+ return false;
1338
+ }
1339
+ }
1340
+ return true;
1341
+ }
1342
+ function isOpenGoatPluginId(pluginId) {
1343
+ return [
1344
+ OPENCLAW_OPENGOAT_PLUGIN_ID,
1345
+ OPENCLAW_OPENGOAT_PLUGIN_ROOT_ID,
1346
+ OPENCLAW_OPENGOAT_PLUGIN_LEGACY_PACK_ID,
1347
+ OPENCLAW_OPENGOAT_PLUGIN_FALLBACK_ID,
1348
+ ].includes(pluginId.trim().toLowerCase());
1349
+ }
1350
+ function isPluginNotFoundMessage(message) {
1351
+ return message.toLowerCase().includes("plugin not found");
1219
1352
  }
1220
1353
  //# sourceMappingURL=opengoat.service.js.map