@opengoat/core 2026.2.9 → 2026.2.14

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 (199) hide show
  1. package/README.md +3 -3
  2. package/dist/core/acp/application/acp-agent.js +4 -8
  3. package/dist/core/acp/application/acp-agent.js.map +1 -1
  4. package/dist/core/agents/application/agent-manifest.service.d.ts +1 -4
  5. package/dist/core/agents/application/agent-manifest.service.js +39 -52
  6. package/dist/core/agents/application/agent-manifest.service.js.map +1 -1
  7. package/dist/core/agents/application/agent.service.d.ts +43 -4
  8. package/dist/core/agents/application/agent.service.js +476 -55
  9. package/dist/core/agents/application/agent.service.js.map +1 -1
  10. package/dist/core/agents/domain/agent-manifest.d.ts +10 -4
  11. package/dist/core/agents/domain/agent-manifest.js +118 -25
  12. package/dist/core/agents/domain/agent-manifest.js.map +1 -1
  13. package/dist/core/agents/index.d.ts +1 -3
  14. package/dist/core/agents/index.js +1 -3
  15. package/dist/core/agents/index.js.map +1 -1
  16. package/dist/core/boards/application/board.service.d.ts +64 -0
  17. package/dist/core/boards/application/board.service.js +590 -0
  18. package/dist/core/boards/application/board.service.js.map +1 -0
  19. package/dist/core/boards/domain/board.d.ts +30 -0
  20. package/dist/core/boards/domain/board.js +2 -0
  21. package/dist/core/boards/domain/board.js.map +1 -0
  22. package/dist/core/boards/index.d.ts +2 -0
  23. package/dist/core/boards/index.js +2 -0
  24. package/dist/core/boards/index.js.map +1 -0
  25. package/dist/core/bootstrap/application/bootstrap.service.d.ts +5 -2
  26. package/dist/core/bootstrap/application/bootstrap.service.js +32 -17
  27. package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -1
  28. package/dist/core/domain/agent-id.d.ts +1 -1
  29. package/dist/core/domain/agent-id.js +1 -1
  30. package/dist/core/domain/agent-id.js.map +1 -1
  31. package/dist/core/domain/agent.d.ts +31 -8
  32. package/dist/core/domain/opengoat-paths.d.ts +1 -0
  33. package/dist/core/opengoat/application/opengoat.service.d.ts +111 -27
  34. package/dist/core/opengoat/application/opengoat.service.js +1048 -136
  35. package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
  36. package/dist/core/opengoat/index.d.ts +1 -0
  37. package/dist/core/orchestration/application/orchestration.service.d.ts +8 -21
  38. package/dist/core/orchestration/application/orchestration.service.js +59 -871
  39. package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
  40. package/dist/core/orchestration/application/routing.service.js +10 -8
  41. package/dist/core/orchestration/application/routing.service.js.map +1 -1
  42. package/dist/core/orchestration/domain/routing.d.ts +0 -17
  43. package/dist/core/orchestration/domain/run-events.d.ts +1 -1
  44. package/dist/core/orchestration/index.d.ts +0 -2
  45. package/dist/core/orchestration/index.js +0 -1
  46. package/dist/core/orchestration/index.js.map +1 -1
  47. package/dist/core/providers/application/provider.service.d.ts +16 -32
  48. package/dist/core/providers/application/provider.service.js +178 -202
  49. package/dist/core/providers/application/provider.service.js.map +1 -1
  50. package/dist/core/providers/cli-provider.d.ts +1 -0
  51. package/dist/core/providers/cli-provider.js +66 -1
  52. package/dist/core/providers/cli-provider.js.map +1 -1
  53. package/dist/core/providers/image-input.d.ts +16 -0
  54. package/dist/core/providers/image-input.js +158 -0
  55. package/dist/core/providers/image-input.js.map +1 -0
  56. package/dist/core/providers/index.d.ts +1 -7
  57. package/dist/core/providers/index.js +0 -13
  58. package/dist/core/providers/index.js.map +1 -1
  59. package/dist/core/providers/loader.js +1 -95
  60. package/dist/core/providers/loader.js.map +1 -1
  61. package/dist/core/providers/providers/openclaw/provider.js +213 -32
  62. package/dist/core/providers/providers/openclaw/provider.js.map +1 -1
  63. package/dist/core/providers/types.d.ts +8 -3
  64. package/dist/core/scenarios/application/scenario-runner.service.d.ts +0 -1
  65. package/dist/core/scenarios/application/scenario-runner.service.js +30 -87
  66. package/dist/core/scenarios/application/scenario-runner.service.js.map +1 -1
  67. package/dist/core/scenarios/domain/scenario.d.ts +0 -15
  68. package/dist/core/sessions/application/session.service.d.ts +10 -4
  69. package/dist/core/sessions/application/session.service.js +87 -89
  70. package/dist/core/sessions/application/session.service.js.map +1 -1
  71. package/dist/core/sessions/domain/session.d.ts +3 -3
  72. package/dist/core/sessions/domain/transcript.d.ts +1 -1
  73. package/dist/core/sessions/index.d.ts +1 -1
  74. package/dist/core/sessions/index.js.map +1 -1
  75. package/dist/core/skills/application/skill.service.d.ts +2 -3
  76. package/dist/core/skills/application/skill.service.js +153 -56
  77. package/dist/core/skills/application/skill.service.js.map +1 -1
  78. package/dist/core/skills/domain/skill.d.ts +2 -2
  79. package/dist/core/skills/domain/skill.js +4 -2
  80. package/dist/core/skills/domain/skill.js.map +1 -1
  81. package/dist/core/templates/assets/ceo/BOOTSTRAP.md +41 -0
  82. package/dist/core/templates/assets/ceo/ROLE.md +15 -0
  83. package/dist/core/templates/assets/organization/MISSION.md +73 -0
  84. package/dist/core/templates/assets/organization/STRATEGY.md +107 -0
  85. package/dist/core/templates/assets/organization/VISION.md +80 -0
  86. package/dist/core/templates/assets/organization/wiki/index.md +15 -0
  87. package/dist/core/templates/assets/skills/og-board-individual/SKILL.md +148 -0
  88. package/dist/core/templates/assets/skills/og-board-manager/SKILL.md +140 -0
  89. package/dist/core/templates/default-templates.d.ts +17 -14
  90. package/dist/core/templates/default-templates.js +115 -212
  91. package/dist/core/templates/default-templates.js.map +1 -1
  92. package/dist/index.d.ts +1 -4
  93. package/dist/index.js +1 -4
  94. package/dist/index.js.map +1 -1
  95. package/dist/platform/node/node-path.port.js +1 -0
  96. package/dist/platform/node/node-path.port.js.map +1 -1
  97. package/package.json +16 -13
  98. package/dist/core/agents/application/workspace-context.service.d.ts +0 -28
  99. package/dist/core/agents/application/workspace-context.service.js +0 -157
  100. package/dist/core/agents/application/workspace-context.service.js.map +0 -1
  101. package/dist/core/agents/domain/workspace-context.d.ts +0 -13
  102. package/dist/core/agents/domain/workspace-context.js +0 -14
  103. package/dist/core/agents/domain/workspace-context.js.map +0 -1
  104. package/dist/core/gateway/domain/protocol.d.ts +0 -113
  105. package/dist/core/gateway/domain/protocol.js +0 -394
  106. package/dist/core/gateway/domain/protocol.js.map +0 -1
  107. package/dist/core/gateway/index.d.ts +0 -2
  108. package/dist/core/gateway/index.js +0 -2
  109. package/dist/core/gateway/index.js.map +0 -1
  110. package/dist/core/llm/application/vercel-ai-text-runtime.d.ts +0 -26
  111. package/dist/core/llm/application/vercel-ai-text-runtime.js +0 -223
  112. package/dist/core/llm/application/vercel-ai-text-runtime.js.map +0 -1
  113. package/dist/core/llm/domain/text-runtime.d.ts +0 -22
  114. package/dist/core/llm/domain/text-runtime.js +0 -2
  115. package/dist/core/llm/domain/text-runtime.js.map +0 -1
  116. package/dist/core/llm/index.d.ts +0 -2
  117. package/dist/core/llm/index.js +0 -2
  118. package/dist/core/llm/index.js.map +0 -1
  119. package/dist/core/orchestration/application/orchestration-planner.service.d.ts +0 -28
  120. package/dist/core/orchestration/application/orchestration-planner.service.js +0 -279
  121. package/dist/core/orchestration/application/orchestration-planner.service.js.map +0 -1
  122. package/dist/core/orchestration/domain/loop.d.ts +0 -119
  123. package/dist/core/orchestration/domain/loop.js +0 -2
  124. package/dist/core/orchestration/domain/loop.js.map +0 -1
  125. package/dist/core/plugins/application/plugin.service.d.ts +0 -32
  126. package/dist/core/plugins/application/plugin.service.js +0 -236
  127. package/dist/core/plugins/application/plugin.service.js.map +0 -1
  128. package/dist/core/plugins/domain/openclaw-compat.d.ts +0 -60
  129. package/dist/core/plugins/domain/openclaw-compat.js +0 -9
  130. package/dist/core/plugins/domain/openclaw-compat.js.map +0 -1
  131. package/dist/core/plugins/index.d.ts +0 -3
  132. package/dist/core/plugins/index.js +0 -3
  133. package/dist/core/plugins/index.js.map +0 -1
  134. package/dist/core/providers/onboarding.d.ts +0 -13
  135. package/dist/core/providers/onboarding.js +0 -149
  136. package/dist/core/providers/onboarding.js.map +0 -1
  137. package/dist/core/providers/providers/claude/index.d.ts +0 -4
  138. package/dist/core/providers/providers/claude/index.js +0 -19
  139. package/dist/core/providers/providers/claude/index.js.map +0 -1
  140. package/dist/core/providers/providers/claude/provider.d.ts +0 -8
  141. package/dist/core/providers/providers/claude/provider.js +0 -106
  142. package/dist/core/providers/providers/claude/provider.js.map +0 -1
  143. package/dist/core/providers/providers/codex/index.d.ts +0 -4
  144. package/dist/core/providers/providers/codex/index.js +0 -19
  145. package/dist/core/providers/providers/codex/index.js.map +0 -1
  146. package/dist/core/providers/providers/codex/provider.d.ts +0 -7
  147. package/dist/core/providers/providers/codex/provider.js +0 -31
  148. package/dist/core/providers/providers/codex/provider.js.map +0 -1
  149. package/dist/core/providers/providers/cursor/index.d.ts +0 -4
  150. package/dist/core/providers/providers/cursor/index.js +0 -19
  151. package/dist/core/providers/providers/cursor/index.js.map +0 -1
  152. package/dist/core/providers/providers/cursor/provider.d.ts +0 -11
  153. package/dist/core/providers/providers/cursor/provider.js +0 -90
  154. package/dist/core/providers/providers/cursor/provider.js.map +0 -1
  155. package/dist/core/providers/providers/extended-http/catalog.d.ts +0 -40
  156. package/dist/core/providers/providers/extended-http/catalog.js +0 -728
  157. package/dist/core/providers/providers/extended-http/catalog.js.map +0 -1
  158. package/dist/core/providers/providers/extended-http/index.d.ts +0 -5
  159. package/dist/core/providers/providers/extended-http/index.js +0 -13
  160. package/dist/core/providers/providers/extended-http/index.js.map +0 -1
  161. package/dist/core/providers/providers/extended-http/provider.d.ts +0 -31
  162. package/dist/core/providers/providers/extended-http/provider.js +0 -580
  163. package/dist/core/providers/providers/extended-http/provider.js.map +0 -1
  164. package/dist/core/providers/providers/gemini/index.d.ts +0 -4
  165. package/dist/core/providers/providers/gemini/index.js +0 -37
  166. package/dist/core/providers/providers/gemini/index.js.map +0 -1
  167. package/dist/core/providers/providers/gemini/provider.d.ts +0 -7
  168. package/dist/core/providers/providers/gemini/provider.js +0 -59
  169. package/dist/core/providers/providers/gemini/provider.js.map +0 -1
  170. package/dist/core/providers/providers/grok/index.d.ts +0 -4
  171. package/dist/core/providers/providers/grok/index.js +0 -41
  172. package/dist/core/providers/providers/grok/index.js.map +0 -1
  173. package/dist/core/providers/providers/grok/provider.d.ts +0 -13
  174. package/dist/core/providers/providers/grok/provider.js +0 -95
  175. package/dist/core/providers/providers/grok/provider.js.map +0 -1
  176. package/dist/core/providers/providers/openai/index.d.ts +0 -4
  177. package/dist/core/providers/providers/openai/index.js +0 -45
  178. package/dist/core/providers/providers/openai/index.js.map +0 -1
  179. package/dist/core/providers/providers/openai/provider.d.ts +0 -14
  180. package/dist/core/providers/providers/openai/provider.js +0 -203
  181. package/dist/core/providers/providers/openai/provider.js.map +0 -1
  182. package/dist/core/providers/providers/opencode/index.d.ts +0 -4
  183. package/dist/core/providers/providers/opencode/index.js +0 -23
  184. package/dist/core/providers/providers/opencode/index.js.map +0 -1
  185. package/dist/core/providers/providers/opencode/provider.d.ts +0 -11
  186. package/dist/core/providers/providers/opencode/provider.js +0 -215
  187. package/dist/core/providers/providers/opencode/provider.js.map +0 -1
  188. package/dist/core/providers/providers/openrouter/index.d.ts +0 -4
  189. package/dist/core/providers/providers/openrouter/index.js +0 -37
  190. package/dist/core/providers/providers/openrouter/index.js.map +0 -1
  191. package/dist/core/providers/providers/openrouter/provider.d.ts +0 -13
  192. package/dist/core/providers/providers/openrouter/provider.js +0 -80
  193. package/dist/core/providers/providers/openrouter/provider.js.map +0 -1
  194. package/dist/platform/node/opengoat-gateway-client.d.ts +0 -19
  195. package/dist/platform/node/opengoat-gateway-client.js +0 -194
  196. package/dist/platform/node/opengoat-gateway-client.js.map +0 -1
  197. package/dist/platform/node/opengoat-gateway-server.d.ts +0 -53
  198. package/dist/platform/node/opengoat-gateway-server.js +0 -906
  199. package/dist/platform/node/opengoat-gateway-server.js.map +0 -1
@@ -1,207 +1,426 @@
1
- import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../domain/agent-id.js";
2
- import { createNoopLogger } from "../../logging/index.js";
3
- import { createDefaultProviderRegistry } from "../../providers/index.js";
1
+ import { homedir } from "node:os";
2
+ import path from "node:path";
4
3
  import { AgentManifestService } from "../../agents/application/agent-manifest.service.js";
5
4
  import { AgentService } from "../../agents/application/agent.service.js";
6
- import { WorkspaceContextService } from "../../agents/application/workspace-context.service.js";
5
+ import { BoardService, } from "../../boards/index.js";
7
6
  import { BootstrapService } from "../../bootstrap/application/bootstrap.service.js";
8
- import { OrchestrationService } from "../../orchestration/index.js";
7
+ import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../domain/agent-id.js";
8
+ import { createNoopLogger } from "../../logging/index.js";
9
+ import { OrchestrationService, } from "../../orchestration/index.js";
9
10
  import { ProviderService } from "../../providers/application/provider.service.js";
10
- import { SkillService } from "../../skills/index.js";
11
- import { PluginService } from "../../plugins/index.js";
12
- import { SessionService } from "../../sessions/index.js";
11
+ import { ProviderCommandNotFoundError, createDefaultProviderRegistry, } from "../../providers/index.js";
12
+ import { SessionService, } from "../../sessions/index.js";
13
+ import { SkillService, } from "../../skills/index.js";
14
+ const OPENCLAW_PROVIDER_ID = "openclaw";
15
+ const OPENCLAW_DEFAULT_AGENT_ID = "main";
13
16
  export class OpenGoatService {
17
+ fileSystem;
18
+ pathPort;
14
19
  pathsProvider;
15
20
  agentService;
16
21
  agentManifestService;
17
22
  bootstrapService;
18
23
  providerService;
19
- pluginService;
20
24
  skillService;
21
25
  sessionService;
22
26
  orchestrationService;
27
+ boardService;
28
+ commandRunner;
29
+ nowIso;
30
+ openClawManagedSkillsDirCache;
23
31
  constructor(deps) {
24
32
  const nowIso = deps.nowIso ?? (() => new Date().toISOString());
25
- const rootLogger = (deps.logger ?? createNoopLogger()).child({ scope: "opengoat-service" });
33
+ const rootLogger = (deps.logger ?? createNoopLogger()).child({
34
+ scope: "opengoat-service",
35
+ });
26
36
  const providerRegistryFactory = deps.providerRegistry
27
37
  ? () => deps.providerRegistry
28
38
  : () => createDefaultProviderRegistry();
39
+ this.fileSystem = deps.fileSystem;
40
+ this.pathPort = deps.pathPort;
29
41
  this.pathsProvider = deps.pathsProvider;
42
+ this.nowIso = nowIso;
30
43
  this.agentService = new AgentService({
31
44
  fileSystem: deps.fileSystem,
32
45
  pathPort: deps.pathPort,
33
- nowIso
46
+ nowIso,
34
47
  });
35
48
  this.agentManifestService = new AgentManifestService({
36
49
  fileSystem: deps.fileSystem,
37
- pathPort: deps.pathPort
50
+ pathPort: deps.pathPort,
38
51
  });
39
52
  this.bootstrapService = new BootstrapService({
40
53
  fileSystem: deps.fileSystem,
54
+ pathPort: deps.pathPort,
41
55
  pathsProvider: deps.pathsProvider,
42
56
  agentService: this.agentService,
43
- nowIso
44
- });
45
- const workspaceContextService = new WorkspaceContextService({
46
- fileSystem: deps.fileSystem,
47
- pathPort: deps.pathPort
57
+ nowIso,
48
58
  });
49
- this.pluginService =
50
- deps.pluginService ??
51
- new PluginService({
52
- fileSystem: deps.fileSystem,
53
- pathPort: deps.pathPort,
54
- commandRunner: deps.commandRunner
55
- });
56
59
  this.skillService = new SkillService({
57
60
  fileSystem: deps.fileSystem,
58
61
  pathPort: deps.pathPort,
59
- pluginSkillDirsProvider: (paths) => this.pluginService.resolvePluginSkillDirectories(paths)
60
62
  });
61
63
  this.providerService = new ProviderService({
62
64
  fileSystem: deps.fileSystem,
63
65
  pathPort: deps.pathPort,
64
66
  providerRegistry: providerRegistryFactory,
65
- workspaceContextService,
66
- skillService: this.skillService,
67
67
  nowIso,
68
- logger: rootLogger.child({ scope: "provider" })
68
+ logger: rootLogger.child({ scope: "provider" }),
69
69
  });
70
70
  this.sessionService = new SessionService({
71
71
  fileSystem: deps.fileSystem,
72
72
  pathPort: deps.pathPort,
73
73
  commandRunner: deps.commandRunner,
74
74
  nowIso,
75
- nowMs: () => Date.now()
75
+ nowMs: () => Date.now(),
76
76
  });
77
+ this.commandRunner = deps.commandRunner;
77
78
  this.orchestrationService = new OrchestrationService({
78
79
  providerService: this.providerService,
79
- skillService: this.skillService,
80
80
  agentManifestService: this.agentManifestService,
81
81
  sessionService: this.sessionService,
82
- commandRunner: deps.commandRunner,
83
82
  fileSystem: deps.fileSystem,
84
83
  pathPort: deps.pathPort,
85
84
  nowIso,
86
- logger: rootLogger.child({ scope: "orchestration" })
85
+ logger: rootLogger.child({ scope: "orchestration" }),
86
+ });
87
+ this.boardService = new BoardService({
88
+ fileSystem: deps.fileSystem,
89
+ pathPort: deps.pathPort,
90
+ nowIso,
91
+ agentManifestService: this.agentManifestService,
87
92
  });
88
93
  }
89
94
  initialize() {
90
- return this.bootstrapService.initialize();
95
+ return this.initializeRuntimeDefaults();
91
96
  }
92
- async createAgent(rawName, options = {}) {
93
- const requestedProviderId = options.providerId?.trim();
94
- if (requestedProviderId && options.createExternalAgent === true) {
95
- const supportsRequestedProvider = await this.providerSupportsExternalAgentCreation(requestedProviderId);
96
- if (!supportsRequestedProvider) {
97
- throw new Error(`Provider "${requestedProviderId}" does not support external agent creation.`);
97
+ async hardReset() {
98
+ const paths = this.pathsProvider.getPaths();
99
+ const warnings = [];
100
+ const deletedOpenClawAgents = [];
101
+ const failedOpenClawAgents = [];
102
+ const removedOpenClawManagedSkillDirs = [];
103
+ const candidateOpenClawAgentIds = new Set();
104
+ const localAgents = await this.agentService.listAgents(paths);
105
+ for (const agent of localAgents) {
106
+ if (agent.id === OPENCLAW_DEFAULT_AGENT_ID) {
107
+ continue;
108
+ }
109
+ candidateOpenClawAgentIds.add(agent.id);
110
+ }
111
+ await this.addWorkspaceAgentCandidates(paths, candidateOpenClawAgentIds, warnings);
112
+ try {
113
+ const openClawAgents = await this.listOpenClawAgents(paths);
114
+ for (const entry of openClawAgents) {
115
+ if (entry.id !== OPENCLAW_DEFAULT_AGENT_ID &&
116
+ (pathIsWithin(paths.homeDir, entry.workspace) ||
117
+ pathIsWithin(paths.homeDir, entry.agentDir))) {
118
+ candidateOpenClawAgentIds.add(entry.id);
119
+ }
120
+ }
121
+ }
122
+ catch (error) {
123
+ warnings.push(`OpenClaw agent discovery failed: ${toErrorMessage(error)}`);
124
+ }
125
+ for (const agentId of [...candidateOpenClawAgentIds].sort((left, right) => left.localeCompare(right))) {
126
+ if (agentId === OPENCLAW_DEFAULT_AGENT_ID) {
127
+ continue;
128
+ }
129
+ try {
130
+ const deleted = await this.providerService.deleteProviderAgent(paths, agentId, { providerId: OPENCLAW_PROVIDER_ID });
131
+ if (deleted.code === 0 ||
132
+ containsAgentNotFoundMessage(deleted.stdout, deleted.stderr)) {
133
+ deletedOpenClawAgents.push(agentId);
134
+ continue;
135
+ }
136
+ const failureReason = deleted.stderr.trim() || deleted.stdout.trim();
137
+ if (!failureReason) {
138
+ warnings.push(`OpenClaw delete for "${agentId}" failed with code ${deleted.code}.`);
139
+ continue;
140
+ }
141
+ failedOpenClawAgents.push({
142
+ agentId,
143
+ reason: failureReason,
144
+ });
145
+ }
146
+ catch (error) {
147
+ if (error instanceof ProviderCommandNotFoundError) {
148
+ deletedOpenClawAgents.push(agentId);
149
+ continue;
150
+ }
151
+ failedOpenClawAgents.push({
152
+ agentId,
153
+ reason: toErrorMessage(error),
154
+ });
98
155
  }
99
156
  }
157
+ try {
158
+ const managedSkillsCleanup = await this.removeOpenClawManagedRoleSkills(paths);
159
+ removedOpenClawManagedSkillDirs.push(...managedSkillsCleanup.removedPaths);
160
+ }
161
+ catch (error) {
162
+ warnings.push(`OpenClaw managed skills cleanup failed: ${toErrorMessage(error)}`);
163
+ }
164
+ await this.fileSystem.removeDir(paths.homeDir);
165
+ this.openClawManagedSkillsDirCache = undefined;
166
+ const homeRemoved = !(await this.fileSystem.exists(paths.homeDir));
167
+ return {
168
+ homeDir: paths.homeDir,
169
+ homeRemoved,
170
+ deletedOpenClawAgents,
171
+ failedOpenClawAgents,
172
+ removedOpenClawManagedSkillDirs,
173
+ warnings,
174
+ };
175
+ }
176
+ async syncRuntimeDefaults() {
177
+ const paths = this.pathsProvider.getPaths();
178
+ const warnings = [];
179
+ let ceoSynced = false;
180
+ 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, {
184
+ id: DEFAULT_AGENT_ID,
185
+ displayName: "CEO",
186
+ }, {
187
+ type: "manager",
188
+ reportsTo: null,
189
+ role: "CEO",
190
+ });
191
+ ceoDescriptor = created.agent;
192
+ }
193
+ try {
194
+ await this.syncOpenClawRoleSkills(paths, DEFAULT_AGENT_ID);
195
+ }
196
+ catch (error) {
197
+ warnings.push(`OpenClaw role skill assignment sync for "ceo" failed: ${toErrorMessage(error)}`);
198
+ }
199
+ 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
+ }
213
+ }
214
+ catch (error) {
215
+ warnings.push(`OpenClaw sync for "ceo" failed: ${toErrorMessage(error)}`);
216
+ }
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,
224
+ });
225
+ }
226
+ catch (error) {
227
+ warnings.push(`OpenClaw ceo location sync failed: ${toErrorMessage(error)}`);
228
+ }
229
+ }
230
+ try {
231
+ await this.agentService.ensureCeoWorkspaceBootstrap(paths);
232
+ }
233
+ catch (error) {
234
+ warnings.push(`OpenGoat workspace bootstrap for "ceo" failed: ${toErrorMessage(error)}`);
235
+ }
236
+ return {
237
+ ceoSyncCode,
238
+ ceoSynced,
239
+ warnings,
240
+ };
241
+ }
242
+ async createAgent(rawName, options = {}) {
100
243
  const identity = this.agentService.normalizeAgentName(rawName);
101
244
  const paths = this.pathsProvider.getPaths();
102
- const created = await this.agentService.ensureAgent(paths, identity);
103
- let binding;
104
- if (requestedProviderId) {
105
- binding = await this.providerService.setAgentProvider(paths, created.agent.id, requestedProviderId);
106
- await this.agentManifestService.syncManifestProvider(paths, created.agent.id, binding.providerId);
107
- }
108
- const shouldCreateExternalAgentByDefault = Boolean(requestedProviderId)
109
- ? options.createExternalAgent !== false
110
- : false;
111
- const shouldCreateExternalAgentExplicitlyWithoutProvider = !requestedProviderId && options.createExternalAgent === true;
112
- if (!shouldCreateExternalAgentByDefault && !shouldCreateExternalAgentExplicitlyWithoutProvider) {
113
- return created;
114
- }
115
- const resolvedBinding = binding ?? (await this.providerService.getAgentProvider(paths, created.agent.id));
116
- const supportsExternalAgentCreation = await this.providerSupportsExternalAgentCreation(resolvedBinding.providerId);
117
- if (options.createExternalAgent === true && !supportsExternalAgentCreation) {
118
- throw new Error(`Provider "${resolvedBinding.providerId}" does not support external agent creation.`);
119
- }
120
- if (!supportsExternalAgentCreation) {
121
- return created;
122
- }
123
- const externalAgentCreation = await this.providerService.createProviderAgent(paths, created.agent.id, {
124
- providerId: resolvedBinding.providerId,
245
+ const created = await this.agentService.ensureAgent(paths, identity, {
246
+ type: options.type,
247
+ reportsTo: options.reportsTo,
248
+ skills: options.skills,
249
+ role: options.role,
250
+ });
251
+ try {
252
+ const workspaceSkillSync = await this.syncOpenClawRoleSkills(paths, created.agent.id);
253
+ created.createdPaths.push(...workspaceSkillSync.createdPaths);
254
+ created.skippedPaths.push(...workspaceSkillSync.skippedPaths);
255
+ created.skippedPaths.push(...workspaceSkillSync.removedPaths);
256
+ }
257
+ catch (error) {
258
+ if (!created.alreadyExisted) {
259
+ await this.agentService.removeAgent(paths, created.agent.id);
260
+ }
261
+ throw new Error(`Failed to sync OpenClaw role skills for "${created.agent.id}". ${toErrorMessage(error)}`);
262
+ }
263
+ const runtimeSync = await this.providerService.createProviderAgent(paths, created.agent.id, {
264
+ providerId: OPENCLAW_PROVIDER_ID,
125
265
  displayName: created.agent.displayName,
126
266
  workspaceDir: created.agent.workspaceDir,
127
- internalConfigDir: created.agent.internalConfigDir
267
+ internalConfigDir: created.agent.internalConfigDir,
128
268
  });
269
+ if (runtimeSync.code !== 0 &&
270
+ !containsAlreadyExistsMessage(runtimeSync.stdout, runtimeSync.stderr)) {
271
+ if (!created.alreadyExisted) {
272
+ await this.agentService.removeAgent(paths, created.agent.id);
273
+ }
274
+ throw new Error(`OpenClaw agent creation failed for "${created.agent.id}" (exit ${runtimeSync.code}). ${runtimeSync.stderr.trim() || runtimeSync.stdout.trim() || ""}`.trim());
275
+ }
276
+ try {
277
+ await this.ensureOpenClawAgentLocation(paths, {
278
+ agentId: created.agent.id,
279
+ displayName: created.agent.displayName,
280
+ workspaceDir: created.agent.workspaceDir,
281
+ internalConfigDir: created.agent.internalConfigDir,
282
+ });
283
+ }
284
+ catch (error) {
285
+ if (!created.alreadyExisted) {
286
+ await this.agentService.removeAgent(paths, created.agent.id);
287
+ }
288
+ throw new Error(`OpenClaw agent location sync failed for "${created.agent.id}". ${toErrorMessage(error)}`);
289
+ }
290
+ try {
291
+ const workspaceBootstrap = await this.agentService.ensureAgentWorkspaceBootstrap(paths, {
292
+ agentId: created.agent.id,
293
+ displayName: created.agent.displayName,
294
+ role: options.role?.trim() ??
295
+ (created.alreadyExisted ? created.agent.role : ""),
296
+ });
297
+ created.createdPaths.push(...workspaceBootstrap.createdPaths);
298
+ created.skippedPaths.push(...workspaceBootstrap.skippedPaths);
299
+ created.skippedPaths.push(...workspaceBootstrap.removedPaths);
300
+ }
301
+ catch (error) {
302
+ if (!created.alreadyExisted) {
303
+ await this.agentService.removeAgent(paths, created.agent.id);
304
+ }
305
+ throw new Error(`Failed to update workspace bootstrap for "${created.agent.id}". ${toErrorMessage(error)}`);
306
+ }
129
307
  return {
130
308
  ...created,
131
- externalAgentCreation: {
132
- providerId: externalAgentCreation.providerId,
133
- code: externalAgentCreation.code,
134
- stdout: externalAgentCreation.stdout,
135
- stderr: externalAgentCreation.stderr
136
- }
309
+ runtimeSync: {
310
+ runtimeId: runtimeSync.providerId,
311
+ code: runtimeSync.code,
312
+ stdout: runtimeSync.stdout,
313
+ stderr: runtimeSync.stderr,
314
+ },
137
315
  };
138
316
  }
139
- async createExternalAgent(rawAgentId, options = {}) {
317
+ async deleteAgent(rawAgentId, options = {}) {
140
318
  const paths = this.pathsProvider.getPaths();
141
319
  const agentId = normalizeAgentId(rawAgentId);
142
320
  if (!agentId) {
143
321
  throw new Error("Agent id cannot be empty.");
144
322
  }
145
- const agent = (await this.agentService.listAgents(paths)).find((entry) => entry.id === agentId);
146
- if (!agent) {
147
- throw new Error(`Agent "${agentId}" was not found.`);
148
- }
149
- const resolvedProviderId = options.providerId?.trim().toLowerCase() ??
150
- (await this.providerService.getAgentProvider(paths, agentId)).providerId;
151
- const supportsExternalAgentCreation = await this.providerSupportsExternalAgentCreation(resolvedProviderId);
152
- if (!supportsExternalAgentCreation) {
153
- throw new Error(`Provider "${resolvedProviderId}" does not support external agent creation.`);
154
- }
155
- const externalAgentCreation = await this.providerService.createProviderAgent(paths, agentId, {
156
- providerId: resolvedProviderId,
157
- displayName: agent.displayName,
158
- workspaceDir: agent.workspaceDir,
159
- internalConfigDir: agent.internalConfigDir
323
+ const existing = (await this.agentService.listAgents(paths)).find((entry) => entry.id === agentId);
324
+ if (!existing) {
325
+ return this.agentService.removeAgent(paths, agentId);
326
+ }
327
+ const runtimeSync = await this.providerService.deleteProviderAgent(paths, agentId, {
328
+ providerId: OPENCLAW_PROVIDER_ID,
160
329
  });
330
+ if (runtimeSync.code !== 0 && !options.force) {
331
+ throw new Error(`OpenClaw agent deletion failed for "${agentId}" (exit ${runtimeSync.code}). ${runtimeSync.stderr.trim() || runtimeSync.stdout.trim() || ""}`.trim());
332
+ }
333
+ const removed = await this.agentService.removeAgent(paths, agentId);
161
334
  return {
162
- providerId: externalAgentCreation.providerId,
163
- code: externalAgentCreation.code,
164
- stdout: externalAgentCreation.stdout,
165
- stderr: externalAgentCreation.stderr
335
+ ...removed,
336
+ runtimeSync: {
337
+ runtimeId: runtimeSync.providerId,
338
+ code: runtimeSync.code,
339
+ stdout: runtimeSync.stdout,
340
+ stderr: runtimeSync.stderr,
341
+ },
166
342
  };
167
343
  }
168
- async providerSupportsExternalAgentCreation(providerId) {
169
- const providers = await this.providerService.listProviders();
170
- const provider = providers.find((entry) => entry.id === providerId);
171
- return Boolean(provider?.capabilities.agentCreate);
344
+ async setAgentManager(rawAgentId, rawReportsTo) {
345
+ const paths = this.pathsProvider.getPaths();
346
+ const updated = await this.agentService.setAgentManager(paths, rawAgentId, rawReportsTo);
347
+ await this.syncOpenClawRoleSkills(paths, updated.agentId);
348
+ if (updated.previousReportsTo) {
349
+ await this.syncOpenClawRoleSkills(paths, updated.previousReportsTo);
350
+ }
351
+ if (updated.reportsTo) {
352
+ await this.syncOpenClawRoleSkills(paths, updated.reportsTo);
353
+ }
354
+ return updated;
355
+ }
356
+ async listAgents() {
357
+ const paths = this.pathsProvider.getPaths();
358
+ return this.agentService.listAgents(paths);
172
359
  }
173
- async deleteAgent(rawAgentId, options = {}) {
360
+ async listDirectReportees(rawAgentId) {
361
+ const agentId = normalizeAgentId(rawAgentId);
362
+ if (!agentId) {
363
+ throw new Error("Agent id cannot be empty.");
364
+ }
174
365
  const paths = this.pathsProvider.getPaths();
366
+ const manifests = await this.agentManifestService.listManifests(paths);
367
+ assertAgentExists(manifests, agentId);
368
+ return manifests
369
+ .filter((manifest) => manifest.metadata.reportsTo === agentId)
370
+ .map((manifest) => manifest.agentId)
371
+ .sort((left, right) => left.localeCompare(right));
372
+ }
373
+ async listAllReportees(rawAgentId) {
175
374
  const agentId = normalizeAgentId(rawAgentId);
176
375
  if (!agentId) {
177
376
  throw new Error("Agent id cannot be empty.");
178
377
  }
179
- let externalProviderId = options.providerId?.trim().toLowerCase();
180
- if (options.deleteExternalAgent && !externalProviderId) {
181
- const binding = await this.providerService.getAgentProvider(paths, agentId);
182
- externalProviderId = binding.providerId;
378
+ const paths = this.pathsProvider.getPaths();
379
+ const manifests = await this.agentManifestService.listManifests(paths);
380
+ assertAgentExists(manifests, agentId);
381
+ return collectAllReportees(manifests, agentId);
382
+ }
383
+ async getAgentInfo(rawAgentId) {
384
+ const agentId = normalizeAgentId(rawAgentId);
385
+ if (!agentId) {
386
+ throw new Error("Agent id cannot be empty.");
183
387
  }
184
- const removed = await this.agentService.removeAgent(paths, agentId);
185
- if (!options.deleteExternalAgent) {
186
- return removed;
388
+ const paths = this.pathsProvider.getPaths();
389
+ const [agents, manifests] = await Promise.all([
390
+ this.agentService.listAgents(paths),
391
+ this.agentManifestService.listManifests(paths),
392
+ ]);
393
+ const descriptorsById = new Map(agents.map((agent) => [agent.id, agent]));
394
+ const agent = descriptorsById.get(agentId);
395
+ if (!agent) {
396
+ throw new Error(`Agent "${agentId}" does not exist.`);
187
397
  }
188
- const externalAgentDeletion = await this.providerService.deleteProviderAgent(paths, agentId, {
189
- providerId: externalProviderId
190
- });
398
+ const totalReportees = collectAllReportees(manifests, agentId).length;
399
+ const directReportees = manifests
400
+ .filter((manifest) => manifest.metadata.reportsTo === agentId)
401
+ .map((manifest) => {
402
+ const descriptor = descriptorsById.get(manifest.agentId);
403
+ const name = descriptor?.displayName?.trim() ||
404
+ manifest.metadata.name ||
405
+ manifest.agentId;
406
+ const role = descriptor?.role?.trim() || manifest.metadata.description || "Agent";
407
+ return {
408
+ id: manifest.agentId,
409
+ name,
410
+ role,
411
+ totalReportees: collectAllReportees(manifests, manifest.agentId)
412
+ .length,
413
+ };
414
+ })
415
+ .sort((left, right) => left.id.localeCompare(right.id));
191
416
  return {
192
- ...removed,
193
- externalAgentDeletion: {
194
- providerId: externalAgentDeletion.providerId,
195
- code: externalAgentDeletion.code,
196
- stdout: externalAgentDeletion.stdout,
197
- stderr: externalAgentDeletion.stderr
198
- }
417
+ id: agent.id,
418
+ name: agent.displayName,
419
+ role: agent.role,
420
+ totalReportees,
421
+ directReportees,
199
422
  };
200
423
  }
201
- async listAgents() {
202
- const paths = this.pathsProvider.getPaths();
203
- return this.agentService.listAgents(paths);
204
- }
205
424
  listProviders() {
206
425
  return this.providerService.listProviders();
207
426
  }
@@ -226,9 +445,15 @@ export class OpenGoatService {
226
445
  }
227
446
  async setAgentProvider(agentId, providerId) {
228
447
  const paths = this.pathsProvider.getPaths();
229
- const binding = await this.providerService.setAgentProvider(paths, agentId, providerId);
230
- await this.agentManifestService.syncManifestProvider(paths, binding.agentId, binding.providerId);
231
- return binding;
448
+ return this.providerService.setAgentProvider(paths, agentId, providerId);
449
+ }
450
+ async getOpenClawGatewayConfig() {
451
+ const paths = this.pathsProvider.getPaths();
452
+ return this.providerService.getOpenClawGatewayConfig(paths, process.env);
453
+ }
454
+ async setOpenClawGatewayConfig(config) {
455
+ const paths = this.pathsProvider.getPaths();
456
+ return this.providerService.setOpenClawGatewayConfig(paths, config);
232
457
  }
233
458
  async routeMessage(agentId, message) {
234
459
  const paths = this.pathsProvider.getPaths();
@@ -238,46 +463,201 @@ export class OpenGoatService {
238
463
  const paths = this.pathsProvider.getPaths();
239
464
  return this.orchestrationService.runAgent(paths, agentId, options);
240
465
  }
241
- async listSkills(agentId = DEFAULT_AGENT_ID) {
466
+ async createTask(actorId, options) {
242
467
  const paths = this.pathsProvider.getPaths();
243
- return this.skillService.listSkills(paths, agentId);
468
+ return this.boardService.createTask(paths, actorId, options);
244
469
  }
245
- async listGlobalSkills() {
470
+ async listTasks(options = {}) {
246
471
  const paths = this.pathsProvider.getPaths();
247
- return this.skillService.listGlobalSkills(paths);
472
+ return this.boardService.listTasks(paths, options);
248
473
  }
249
- async installSkill(request) {
474
+ async listLatestTasks(options = {}) {
475
+ const paths = this.pathsProvider.getPaths();
476
+ return this.boardService.listLatestTasks(paths, options);
477
+ }
478
+ async listLatestTasksPage(options = {}) {
479
+ const paths = this.pathsProvider.getPaths();
480
+ return this.boardService.listLatestTasksPage(paths, options);
481
+ }
482
+ async getTask(taskId) {
483
+ const paths = this.pathsProvider.getPaths();
484
+ return this.boardService.getTask(paths, taskId);
485
+ }
486
+ async deleteTasks(actorId, taskIds) {
250
487
  const paths = this.pathsProvider.getPaths();
251
- return this.skillService.installSkill(paths, request);
488
+ return this.boardService.deleteTasks(paths, actorId, taskIds);
252
489
  }
253
- async listPlugins(options = {}) {
490
+ async updateTaskStatus(actorId, taskId, status, reason) {
254
491
  const paths = this.pathsProvider.getPaths();
255
- return this.pluginService.listPlugins(paths, options);
492
+ return this.boardService.updateTaskStatus(paths, actorId, taskId, status, reason);
256
493
  }
257
- async getPluginInfo(pluginId) {
494
+ async addTaskBlocker(actorId, taskId, blocker) {
258
495
  const paths = this.pathsProvider.getPaths();
259
- return this.pluginService.getPluginInfo(paths, pluginId);
496
+ return this.boardService.addTaskBlocker(paths, actorId, taskId, blocker);
260
497
  }
261
- async installPlugin(request) {
498
+ async addTaskArtifact(actorId, taskId, content) {
262
499
  const paths = this.pathsProvider.getPaths();
263
- return this.pluginService.installPlugin(paths, request);
500
+ return this.boardService.addTaskArtifact(paths, actorId, taskId, content);
264
501
  }
265
- async enablePlugin(pluginId) {
502
+ async addTaskWorklog(actorId, taskId, content) {
266
503
  const paths = this.pathsProvider.getPaths();
267
- await this.pluginService.enablePlugin(paths, pluginId);
504
+ return this.boardService.addTaskWorklog(paths, actorId, taskId, content);
268
505
  }
269
- async disablePlugin(pluginId) {
506
+ async runTaskCronCycle(options = {}) {
270
507
  const paths = this.pathsProvider.getPaths();
271
- await this.pluginService.disablePlugin(paths, pluginId);
508
+ const ranAt = this.resolveNowIso();
509
+ const manifests = await this.agentManifestService.listManifests(paths);
510
+ const manifestsById = new Map(manifests.map((manifest) => [manifest.agentId, manifest]));
511
+ const inactiveMinutes = resolveInactiveMinutes(options.inactiveMinutes);
512
+ const notificationTarget = resolveInactiveAgentNotificationTarget(options.notificationTarget);
513
+ const notifyInactiveAgents = options.notifyInactiveAgents ?? true;
514
+ const inactiveCandidates = notifyInactiveAgents
515
+ ? await this.collectInactiveAgents(paths, manifests, inactiveMinutes, notificationTarget)
516
+ : [];
517
+ const latestCeoProjectPath = notifyInactiveAgents
518
+ ? await this.resolveLatestProjectPathForAgent(paths, DEFAULT_AGENT_ID)
519
+ : undefined;
520
+ const tasks = await this.boardService.listTasks(paths, { limit: 10_000 });
521
+ const dispatches = [];
522
+ let scannedTasks = tasks.length;
523
+ let todoTasks = 0;
524
+ let blockedTasks = 0;
525
+ for (const task of tasks) {
526
+ if (task.status !== "todo" && task.status !== "blocked") {
527
+ continue;
528
+ }
529
+ if (task.status === "todo") {
530
+ todoTasks += 1;
531
+ 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({
536
+ kind: "todo",
537
+ targetAgentId,
538
+ sessionRef,
539
+ taskId: task.taskId,
540
+ ok: result.ok,
541
+ error: result.error,
542
+ });
543
+ continue;
544
+ }
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,
557
+ ok: result.ok,
558
+ error: result.error,
559
+ });
560
+ }
561
+ for (const candidate of inactiveCandidates) {
562
+ const sessionRef = buildInactiveSessionRef(candidate.managerAgentId, candidate.subjectAgentId);
563
+ const message = buildInactiveAgentMessage({
564
+ managerAgentId: candidate.managerAgentId,
565
+ subjectAgentId: candidate.subjectAgentId,
566
+ subjectName: candidate.subjectName,
567
+ role: candidate.role,
568
+ inactiveMinutes,
569
+ lastActionTimestamp: candidate.lastActionTimestamp,
570
+ });
571
+ const result = await this.dispatchAutomationMessage(paths, candidate.managerAgentId, sessionRef, message, {
572
+ cwd: latestCeoProjectPath,
573
+ });
574
+ dispatches.push({
575
+ kind: "inactive",
576
+ targetAgentId: candidate.managerAgentId,
577
+ sessionRef,
578
+ subjectAgentId: candidate.subjectAgentId,
579
+ ok: result.ok,
580
+ 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
+ };
594
+ }
595
+ async listSkills(agentId = DEFAULT_AGENT_ID) {
596
+ const paths = this.pathsProvider.getPaths();
597
+ return this.skillService.listSkills(paths, agentId);
598
+ }
599
+ async listGlobalSkills() {
600
+ const paths = this.pathsProvider.getPaths();
601
+ return this.skillService.listGlobalSkills(paths);
272
602
  }
273
- async pluginDoctor() {
603
+ async installSkill(request) {
274
604
  const paths = this.pathsProvider.getPaths();
275
- return this.pluginService.doctor(paths);
605
+ const result = await this.skillService.installSkill(paths, request);
606
+ if (result.scope === "agent" && result.agentId) {
607
+ await this.syncOpenClawRoleSkills(paths, result.agentId);
608
+ }
609
+ return result;
610
+ }
611
+ async runOpenClaw(args, options = {}) {
612
+ if (!this.commandRunner) {
613
+ throw new Error("OpenClaw passthrough is unavailable: command runner was not configured.");
614
+ }
615
+ const sanitized = args.map((value) => value.trim()).filter(Boolean);
616
+ if (sanitized.length === 0) {
617
+ throw new Error("OpenClaw passthrough requires at least one argument.");
618
+ }
619
+ const executionEnv = prepareOpenClawCommandEnv(options.env ?? process.env);
620
+ const command = executionEnv.OPENGOAT_OPENCLAW_CMD?.trim() ||
621
+ executionEnv.OPENCLAW_CMD?.trim() ||
622
+ process.env.OPENGOAT_OPENCLAW_CMD?.trim() ||
623
+ process.env.OPENCLAW_CMD?.trim() ||
624
+ "openclaw";
625
+ try {
626
+ return await this.commandRunner.run({
627
+ command,
628
+ args: sanitized,
629
+ cwd: options.cwd,
630
+ env: executionEnv,
631
+ });
632
+ }
633
+ catch (error) {
634
+ if (isSpawnPermissionOrMissing(error)) {
635
+ throw new ProviderCommandNotFoundError(OPENCLAW_PROVIDER_ID, command);
636
+ }
637
+ throw error;
638
+ }
276
639
  }
277
640
  async listSessions(agentId = DEFAULT_AGENT_ID, options = {}) {
278
641
  const paths = this.pathsProvider.getPaths();
279
642
  return this.sessionService.listSessions(paths, agentId, options);
280
643
  }
644
+ async prepareSession(agentId = DEFAULT_AGENT_ID, options = {}) {
645
+ const paths = this.pathsProvider.getPaths();
646
+ const prepared = await this.sessionService.prepareRunSession(paths, agentId, {
647
+ sessionRef: options.sessionRef,
648
+ projectPath: options.projectPath,
649
+ forceNew: options.forceNew,
650
+ userMessage: "",
651
+ });
652
+ if (!prepared.enabled) {
653
+ throw new Error("Session preparation was disabled.");
654
+ }
655
+ return prepared.info;
656
+ }
657
+ async getAgentLastAction(agentId = DEFAULT_AGENT_ID) {
658
+ const paths = this.pathsProvider.getPaths();
659
+ return this.sessionService.getLastAgentAction(paths, agentId);
660
+ }
281
661
  async getSessionHistory(agentId = DEFAULT_AGENT_ID, options = {}) {
282
662
  const paths = this.pathsProvider.getPaths();
283
663
  return this.sessionService.getSessionHistory(paths, agentId, options);
@@ -304,5 +684,537 @@ export class OpenGoatService {
304
684
  getPaths() {
305
685
  return this.pathsProvider.getPaths();
306
686
  }
687
+ async dispatchAutomationMessage(paths, agentId, sessionRef, message, options = {}) {
688
+ try {
689
+ const result = await this.orchestrationService.runAgent(paths, agentId, {
690
+ message,
691
+ sessionRef,
692
+ cwd: options.cwd,
693
+ env: process.env,
694
+ });
695
+ if (result.code !== 0) {
696
+ return {
697
+ ok: false,
698
+ error: (result.stderr ||
699
+ result.stdout ||
700
+ `Runtime exited with code ${result.code}.`).trim(),
701
+ };
702
+ }
703
+ return { ok: true };
704
+ }
705
+ catch (error) {
706
+ return {
707
+ ok: false,
708
+ error: toErrorMessage(error),
709
+ };
710
+ }
711
+ }
712
+ resolveNowIso() {
713
+ return this.nowIso();
714
+ }
715
+ async initializeRuntimeDefaults() {
716
+ const initialization = await this.bootstrapService.initialize();
717
+ try {
718
+ await this.syncRuntimeDefaults();
719
+ }
720
+ catch {
721
+ // Startup remains functional even if OpenClaw CLI/runtime is unavailable.
722
+ }
723
+ return initialization;
724
+ }
725
+ resolveNowMs() {
726
+ return Date.now();
727
+ }
728
+ async syncOpenClawRoleSkills(paths, rawAgentId) {
729
+ const agentId = normalizeAgentId(rawAgentId);
730
+ if (!agentId) {
731
+ throw new Error("Agent id cannot be empty.");
732
+ }
733
+ const createdPaths = [];
734
+ const skippedPaths = [];
735
+ const removedPaths = [];
736
+ const managedSkillsSync = await this.removeOpenClawManagedRoleSkills(paths);
737
+ createdPaths.push(...managedSkillsSync.createdPaths);
738
+ skippedPaths.push(...managedSkillsSync.skippedPaths);
739
+ removedPaths.push(...managedSkillsSync.removedPaths);
740
+ const syncedAgents = new Set();
741
+ const syncAgent = async (targetAgentId) => {
742
+ if (syncedAgents.has(targetAgentId)) {
743
+ return;
744
+ }
745
+ syncedAgents.add(targetAgentId);
746
+ const sync = await this.agentService.ensureAgentWorkspaceRoleSkills(paths, targetAgentId);
747
+ createdPaths.push(...sync.createdPaths);
748
+ skippedPaths.push(...sync.skippedPaths);
749
+ removedPaths.push(...sync.removedPaths);
750
+ };
751
+ await syncAgent(agentId);
752
+ const manifest = await this.agentManifestService.getManifest(paths, agentId);
753
+ const managerAgentId = normalizeAgentId(manifest.metadata.reportsTo ?? "");
754
+ if (managerAgentId) {
755
+ await syncAgent(managerAgentId);
756
+ }
757
+ return {
758
+ createdPaths,
759
+ skippedPaths,
760
+ removedPaths,
761
+ };
762
+ }
763
+ async removeOpenClawManagedRoleSkills(paths) {
764
+ if (!this.commandRunner) {
765
+ return {
766
+ createdPaths: [],
767
+ skippedPaths: ["openclaw-managed-skills:command-runner-unavailable"],
768
+ removedPaths: [],
769
+ };
770
+ }
771
+ const managedSkillsDir = await this.resolveOpenClawManagedSkillsDir(paths);
772
+ if (!managedSkillsDir) {
773
+ return {
774
+ createdPaths: [],
775
+ skippedPaths: ["openclaw-managed-skills:unresolved"],
776
+ removedPaths: [],
777
+ };
778
+ }
779
+ const skippedPaths = [];
780
+ const removedPaths = [];
781
+ for (const legacySkillId of [
782
+ "board-manager",
783
+ "board-individual",
784
+ "og-board-manager",
785
+ "og-board-individual",
786
+ "manager",
787
+ "board-user",
788
+ ]) {
789
+ const legacyDir = this.pathPort.join(managedSkillsDir, legacySkillId);
790
+ if (!(await this.fileSystem.exists(legacyDir))) {
791
+ skippedPaths.push(legacyDir);
792
+ continue;
793
+ }
794
+ await this.fileSystem.removeDir(legacyDir);
795
+ removedPaths.push(legacyDir);
796
+ }
797
+ return {
798
+ createdPaths: [],
799
+ skippedPaths,
800
+ removedPaths,
801
+ };
802
+ }
803
+ async resolveOpenClawManagedSkillsDir(paths) {
804
+ if (this.openClawManagedSkillsDirCache !== undefined) {
805
+ return this.openClawManagedSkillsDirCache;
806
+ }
807
+ const providerConfig = await this.providerService.getProviderConfig(paths, OPENCLAW_PROVIDER_ID);
808
+ const env = {
809
+ ...process.env,
810
+ ...(providerConfig?.env ?? {}),
811
+ };
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
+ try {
820
+ parsed = JSON.parse(skillsList.stdout);
821
+ }
822
+ catch {
823
+ throw new Error("OpenClaw skills list returned non-JSON output; cannot resolve managed skills directory.");
824
+ }
825
+ const managedSkillsDir = extractManagedSkillsDir(parsed);
826
+ this.openClawManagedSkillsDirCache = managedSkillsDir;
827
+ return managedSkillsDir;
828
+ }
829
+ async listOpenClawAgents(paths) {
830
+ if (!this.commandRunner) {
831
+ return [];
832
+ }
833
+ 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
+ try {
842
+ parsed = JSON.parse(listed.stdout);
843
+ }
844
+ catch {
845
+ throw new Error("OpenClaw agents list returned non-JSON output; cannot inspect agents.");
846
+ }
847
+ return extractOpenClawAgents(parsed);
848
+ }
849
+ async addWorkspaceAgentCandidates(paths, candidates, warnings) {
850
+ try {
851
+ const workspaceDirs = await this.fileSystem.listDirectories(paths.workspacesDir);
852
+ for (const workspaceDir of workspaceDirs) {
853
+ const workspaceAgentId = normalizeAgentId(workspaceDir);
854
+ if (workspaceAgentId &&
855
+ workspaceAgentId !== OPENCLAW_DEFAULT_AGENT_ID) {
856
+ candidates.add(workspaceAgentId);
857
+ }
858
+ }
859
+ }
860
+ catch (error) {
861
+ warnings.push(`Workspace fallback discovery failed: ${toErrorMessage(error)}`);
862
+ }
863
+ }
864
+ async ensureOpenClawAgentLocation(paths, params) {
865
+ if (!this.commandRunner) {
866
+ return;
867
+ }
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());
874
+ }
875
+ let parsed;
876
+ try {
877
+ parsed = JSON.parse(listed.stdout);
878
+ }
879
+ catch {
880
+ throw new Error("OpenClaw agents list returned non-JSON output; cannot verify agent location.");
881
+ }
882
+ const entry = extractOpenClawAgentEntry(parsed, params.agentId);
883
+ if (!entry) {
884
+ return;
885
+ }
886
+ if (pathMatches(entry.workspace, params.workspaceDir) &&
887
+ pathMatches(entry.agentDir, params.internalConfigDir)) {
888
+ return;
889
+ }
890
+ const deleted = await this.providerService.deleteProviderAgent(paths, params.agentId, { providerId: OPENCLAW_PROVIDER_ID });
891
+ if (deleted.code !== 0) {
892
+ throw new Error(`OpenClaw agent location repair failed deleting "${params.agentId}" (exit ${deleted.code}). ${deleted.stderr.trim() || deleted.stdout.trim() || ""}`.trim());
893
+ }
894
+ const recreated = await this.providerService.createProviderAgent(paths, params.agentId, {
895
+ providerId: OPENCLAW_PROVIDER_ID,
896
+ displayName: params.displayName,
897
+ workspaceDir: params.workspaceDir,
898
+ internalConfigDir: params.internalConfigDir,
899
+ });
900
+ if (recreated.code !== 0 &&
901
+ !containsAlreadyExistsMessage(recreated.stdout, recreated.stderr)) {
902
+ throw new Error(`OpenClaw agent location repair failed creating "${params.agentId}" (exit ${recreated.code}). ${recreated.stderr.trim() || recreated.stdout.trim() || ""}`.trim());
903
+ }
904
+ }
905
+ async resolveOpenClawEnv(paths) {
906
+ const providerConfig = await this.providerService.getProviderConfig(paths, OPENCLAW_PROVIDER_ID);
907
+ return {
908
+ ...(providerConfig?.env ?? {}),
909
+ ...process.env,
910
+ };
911
+ }
912
+ async collectInactiveAgents(paths, manifests, inactiveMinutes, notificationTarget) {
913
+ const nowMs = this.resolveNowMs();
914
+ const inactiveCutoffMs = nowMs - inactiveMinutes * 60_000;
915
+ const inactive = [];
916
+ for (const manifest of manifests) {
917
+ const managerAgentId = normalizeAgentId(manifest.metadata.reportsTo ?? "");
918
+ if (!managerAgentId) {
919
+ continue;
920
+ }
921
+ if (notificationTarget === "ceo-only" &&
922
+ managerAgentId !== DEFAULT_AGENT_ID) {
923
+ continue;
924
+ }
925
+ const lastAction = await this.sessionService.getLastAgentAction(paths, manifest.agentId);
926
+ if (lastAction && lastAction.timestamp >= inactiveCutoffMs) {
927
+ continue;
928
+ }
929
+ inactive.push({
930
+ managerAgentId,
931
+ subjectAgentId: manifest.agentId,
932
+ subjectName: manifest.metadata.name,
933
+ role: manifest.metadata.description,
934
+ lastActionTimestamp: lastAction?.timestamp,
935
+ });
936
+ }
937
+ return inactive;
938
+ }
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
+ }
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;
960
+ }
961
+ return String(error);
962
+ }
963
+ function resolveInactiveMinutes(value) {
964
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
965
+ return 30;
966
+ }
967
+ return Math.floor(value);
968
+ }
969
+ function resolveInactiveAgentNotificationTarget(value) {
970
+ return value === "ceo-only" ? "ceo-only" : "all-managers";
971
+ }
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) {
1025
+ return false;
1026
+ }
1027
+ return leftNormalized === rightNormalized;
1028
+ }
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;
1038
+ }
1039
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
1040
+ }
1041
+ function normalizePathForCompare(value) {
1042
+ const trimmed = value.trim();
1043
+ 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) {
1146
+ continue;
1147
+ }
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;
1158
+ }
1159
+ visited.add(current);
1160
+ queue.push(...(byManager.get(current) ?? []));
1161
+ }
1162
+ return [...visited].sort((left, right) => left.localeCompare(right));
1163
+ }
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
+ };
1173
+ }
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) {
1201
+ const seen = new Set();
1202
+ const deduped = [];
1203
+ for (const rawEntry of entries) {
1204
+ const entry = rawEntry.trim();
1205
+ if (!entry || seen.has(entry)) {
1206
+ continue;
1207
+ }
1208
+ seen.add(entry);
1209
+ deduped.push(entry);
1210
+ }
1211
+ return deduped;
1212
+ }
1213
+ function isSpawnPermissionOrMissing(error) {
1214
+ return (typeof error === "object" &&
1215
+ error !== null &&
1216
+ "code" in error &&
1217
+ ((error.code ?? "") === "ENOENT" ||
1218
+ (error.code ?? "") === "EACCES"));
307
1219
  }
308
1220
  //# sourceMappingURL=opengoat.service.js.map