@opengoat/core 2026.2.14 → 2026.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/agents/application/agent.service.d.ts +19 -3
- package/dist/core/agents/application/agent.service.js +170 -45
- package/dist/core/agents/application/agent.service.js.map +1 -1
- package/dist/core/boards/application/board.service.d.ts +2 -0
- package/dist/core/boards/application/board.service.js +36 -2
- package/dist/core/boards/application/board.service.js.map +1 -1
- package/dist/core/bootstrap/application/bootstrap.service.js +17 -1
- package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -1
- package/dist/core/opengoat/application/opengoat.service.d.ts +14 -3
- package/dist/core/opengoat/application/opengoat.service.helpers.d.ts +62 -0
- package/dist/core/opengoat/application/opengoat.service.helpers.js +403 -0
- package/dist/core/opengoat/application/opengoat.service.helpers.js.map +1 -0
- package/dist/core/opengoat/application/opengoat.service.js +653 -371
- package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
- package/dist/core/orchestration/application/orchestration.service.d.ts +2 -0
- package/dist/core/orchestration/application/orchestration.service.js +150 -52
- package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
- package/dist/core/providers/application/provider.service.d.ts +35 -4
- package/dist/core/providers/application/provider.service.js +823 -96
- package/dist/core/providers/application/provider.service.js.map +1 -1
- package/dist/core/providers/index.d.ts +1 -1
- package/dist/core/providers/index.js.map +1 -1
- package/dist/core/providers/loader.js +4 -2
- package/dist/core/providers/loader.js.map +1 -1
- package/dist/core/providers/openclaw-gateway-rpc.d.ts +20 -0
- package/dist/core/providers/openclaw-gateway-rpc.js +629 -0
- package/dist/core/providers/openclaw-gateway-rpc.js.map +1 -0
- package/dist/core/providers/provider-module.d.ts +11 -0
- package/dist/core/providers/provider-module.js +8 -1
- package/dist/core/providers/provider-module.js.map +1 -1
- package/dist/core/providers/providers/claude-code/index.d.ts +4 -0
- package/dist/core/providers/providers/claude-code/index.js +32 -0
- package/dist/core/providers/providers/claude-code/index.js.map +1 -0
- package/dist/core/providers/providers/claude-code/provider.d.ts +13 -0
- package/dist/core/providers/providers/claude-code/provider.js +152 -0
- package/dist/core/providers/providers/claude-code/provider.js.map +1 -0
- package/dist/core/providers/providers/codex/index.d.ts +4 -0
- package/dist/core/providers/providers/codex/index.js +32 -0
- package/dist/core/providers/providers/codex/index.js.map +1 -0
- package/dist/core/providers/providers/codex/provider.d.ts +12 -0
- package/dist/core/providers/providers/codex/provider.js +156 -0
- package/dist/core/providers/providers/codex/provider.js.map +1 -0
- package/dist/core/providers/providers/cursor/index.d.ts +4 -0
- package/dist/core/providers/providers/cursor/index.js +32 -0
- package/dist/core/providers/providers/cursor/index.js.map +1 -0
- package/dist/core/providers/providers/cursor/provider.d.ts +12 -0
- package/dist/core/providers/providers/cursor/provider.js +161 -0
- package/dist/core/providers/providers/cursor/provider.js.map +1 -0
- package/dist/core/providers/providers/gemini-cli/index.d.ts +4 -0
- package/dist/core/providers/providers/gemini-cli/index.js +36 -0
- package/dist/core/providers/providers/gemini-cli/index.js.map +1 -0
- package/dist/core/providers/providers/gemini-cli/provider.d.ts +11 -0
- package/dist/core/providers/providers/gemini-cli/provider.js +122 -0
- package/dist/core/providers/providers/gemini-cli/provider.js.map +1 -0
- package/dist/core/providers/providers/openclaw/index.js +8 -0
- package/dist/core/providers/providers/openclaw/index.js.map +1 -1
- package/dist/core/providers/providers/openclaw/provider.js +1 -0
- package/dist/core/providers/providers/openclaw/provider.js.map +1 -1
- package/dist/core/providers/providers/opencode/index.d.ts +4 -0
- package/dist/core/providers/providers/opencode/index.js +27 -0
- package/dist/core/providers/providers/opencode/index.js.map +1 -0
- package/dist/core/providers/providers/opencode/provider.d.ts +12 -0
- package/dist/core/providers/providers/opencode/provider.js +130 -0
- package/dist/core/providers/providers/opencode/provider.js.map +1 -0
- package/dist/core/providers/providers/registry.d.ts +2 -0
- package/dist/core/providers/providers/registry.js +17 -0
- package/dist/core/providers/providers/registry.js.map +1 -0
- package/dist/core/providers/registry.d.ts +2 -1
- package/dist/core/providers/registry.js +40 -0
- package/dist/core/providers/registry.js.map +1 -1
- package/dist/core/providers/types.d.ts +1 -0
- package/dist/core/scenarios/application/scenario-runner.service.js +2 -1
- package/dist/core/scenarios/application/scenario-runner.service.js.map +1 -1
- package/dist/core/sessions/application/session.service.d.ts +0 -2
- package/dist/core/sessions/application/session.service.js +10 -73
- package/dist/core/sessions/application/session.service.js.map +1 -1
- package/dist/core/sessions/domain/session.d.ts +0 -3
- package/dist/core/sessions/domain/session.js.map +1 -1
- package/dist/core/skills/application/skill.service.js +4 -1
- package/dist/core/skills/application/skill.service.js.map +1 -1
- package/dist/core/templates/assets/ceo/BOOTSTRAP.md +3 -3
- package/dist/core/templates/assets/ceo/ROLE.md +5 -3
- package/dist/core/templates/assets/skills/og-board-individual/SKILL.md +66 -89
- package/dist/core/templates/assets/skills/og-board-manager/SKILL.md +64 -45
- package/dist/core/templates/assets/skills/og-boards/SKILL.md +65 -0
- package/dist/core/templates/default-templates.d.ts +1 -2
- package/dist/core/templates/default-templates.js +22 -10
- package/dist/core/templates/default-templates.js.map +1 -1
- package/package.json +3 -2
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { normalizeAgentId } from "../../domain/agent-id.js";
|
|
1
2
|
import { createNoopLogger } from "../../logging/index.js";
|
|
2
3
|
import { executeCommand } from "../command-executor.js";
|
|
3
|
-
import {
|
|
4
|
+
import { callOpenClawGatewayRpc, resolveGatewayAgentCallTimeoutMs, } from "../openclaw-gateway-rpc.js";
|
|
5
|
+
import { AgentConfigNotFoundError, InvalidAgentConfigError, InvalidProviderConfigError, ProviderCommandNotFoundError, UnsupportedProviderActionError } from "../index.js";
|
|
4
6
|
const OPENCLAW_PROVIDER_ID = "openclaw";
|
|
7
|
+
const AGENT_CONFIG_FILE_NAME = "config.json";
|
|
8
|
+
const PROVIDER_SESSION_BINDINGS_SCHEMA_VERSION = 1;
|
|
9
|
+
const OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS = 4;
|
|
10
|
+
const OPENCLAW_RETRY_BACKOFF_BASE_MS = 100;
|
|
11
|
+
const OPENCLAW_RETRY_BACKOFF_MAX_MS = 1_500;
|
|
5
12
|
export class ProviderService {
|
|
6
13
|
fileSystem;
|
|
7
14
|
pathPort;
|
|
@@ -18,27 +25,26 @@ export class ProviderService {
|
|
|
18
25
|
}
|
|
19
26
|
async listProviders() {
|
|
20
27
|
const registry = await this.getProviderRegistry();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
capabilities: provider.capabilities
|
|
28
|
-
}
|
|
29
|
-
];
|
|
28
|
+
return registry.listProviders().map((provider) => ({
|
|
29
|
+
id: provider.id,
|
|
30
|
+
displayName: provider.displayName,
|
|
31
|
+
kind: provider.kind,
|
|
32
|
+
capabilities: provider.capabilities
|
|
33
|
+
}));
|
|
30
34
|
}
|
|
31
35
|
async getProviderOnboarding(providerId) {
|
|
32
|
-
|
|
36
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
33
37
|
const registry = await this.getProviderRegistry();
|
|
34
|
-
return registry.getProviderOnboarding(
|
|
38
|
+
return registry.getProviderOnboarding(normalizedProviderId);
|
|
35
39
|
}
|
|
36
40
|
async invokeProviderAuth(paths, providerId, options = {}) {
|
|
37
|
-
|
|
41
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
38
42
|
const registry = await this.getProviderRegistry();
|
|
39
|
-
const provider = registry.create(
|
|
40
|
-
const env = await this.resolveProviderEnv(paths, options.env);
|
|
41
|
-
this.logger.info("Invoking
|
|
43
|
+
const provider = registry.create(normalizedProviderId);
|
|
44
|
+
const env = await this.resolveProviderEnv(paths, normalizedProviderId, options.env);
|
|
45
|
+
this.logger.info("Invoking provider auth.", {
|
|
46
|
+
providerId: provider.id
|
|
47
|
+
});
|
|
42
48
|
return provider.invokeAuth?.({
|
|
43
49
|
...options,
|
|
44
50
|
env
|
|
@@ -49,8 +55,8 @@ export class ProviderService {
|
|
|
49
55
|
};
|
|
50
56
|
}
|
|
51
57
|
async getProviderConfig(paths, providerId) {
|
|
52
|
-
|
|
53
|
-
const configPath = this.getProviderConfigPath(paths);
|
|
58
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
59
|
+
const configPath = this.getProviderConfigPath(paths, normalizedProviderId);
|
|
54
60
|
const exists = await this.fileSystem.exists(configPath);
|
|
55
61
|
if (!exists) {
|
|
56
62
|
return null;
|
|
@@ -61,18 +67,21 @@ export class ProviderService {
|
|
|
61
67
|
parsed = JSON.parse(raw);
|
|
62
68
|
}
|
|
63
69
|
catch {
|
|
64
|
-
throw new InvalidProviderConfigError(
|
|
70
|
+
throw new InvalidProviderConfigError(normalizedProviderId, configPath);
|
|
65
71
|
}
|
|
66
72
|
if (!isProviderStoredConfig(parsed)) {
|
|
67
|
-
throw new InvalidProviderConfigError(
|
|
73
|
+
throw new InvalidProviderConfigError(normalizedProviderId, configPath, "schema mismatch");
|
|
74
|
+
}
|
|
75
|
+
if (parsed.providerId.trim().toLowerCase() !== normalizedProviderId) {
|
|
76
|
+
throw new InvalidProviderConfigError(normalizedProviderId, configPath, `provider id mismatch (found "${parsed.providerId}")`);
|
|
68
77
|
}
|
|
69
78
|
return parsed;
|
|
70
79
|
}
|
|
71
80
|
async setProviderConfig(paths, providerId, env, options = {}) {
|
|
72
|
-
|
|
73
|
-
const providerDir = this.pathPort.join(paths.providersDir,
|
|
74
|
-
const configPath = this.getProviderConfigPath(paths);
|
|
75
|
-
const existing = await this.getProviderConfig(paths,
|
|
81
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
82
|
+
const providerDir = this.pathPort.join(paths.providersDir, normalizedProviderId);
|
|
83
|
+
const configPath = this.getProviderConfigPath(paths, normalizedProviderId);
|
|
84
|
+
const existing = await this.getProviderConfig(paths, normalizedProviderId);
|
|
76
85
|
const replace = options.replace ?? false;
|
|
77
86
|
const mergedEnv = sanitizeEnvMap({
|
|
78
87
|
...(replace ? {} : (existing?.env ?? {})),
|
|
@@ -80,7 +89,7 @@ export class ProviderService {
|
|
|
80
89
|
});
|
|
81
90
|
const next = {
|
|
82
91
|
schemaVersion: 1,
|
|
83
|
-
providerId:
|
|
92
|
+
providerId: normalizedProviderId,
|
|
84
93
|
env: mergedEnv,
|
|
85
94
|
updatedAt: this.nowIso()
|
|
86
95
|
};
|
|
@@ -89,7 +98,7 @@ export class ProviderService {
|
|
|
89
98
|
return next;
|
|
90
99
|
}
|
|
91
100
|
async getOpenClawGatewayConfig(paths, inputEnv) {
|
|
92
|
-
const env = await this.resolveProviderEnv(paths, inputEnv);
|
|
101
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
93
102
|
const explicitMode = env.OPENGOAT_OPENCLAW_GATEWAY_MODE?.trim().toLowerCase();
|
|
94
103
|
const parsedArgs = parseOpenClawArguments(env.OPENCLAW_ARGUMENTS ?? "");
|
|
95
104
|
const mode = explicitMode === "external" || parsedArgs.remoteUrl || parsedArgs.token ? "external" : "local";
|
|
@@ -125,135 +134,300 @@ export class ProviderService {
|
|
|
125
134
|
await this.setProviderConfig(paths, OPENCLAW_PROVIDER_ID, nextEnv, { replace: true });
|
|
126
135
|
return this.getOpenClawGatewayConfig(paths);
|
|
127
136
|
}
|
|
128
|
-
async getAgentProvider(
|
|
137
|
+
async getAgentProvider(paths, agentId) {
|
|
138
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
139
|
+
const providerId = await this.resolveAgentProviderId(paths, normalizedAgentId);
|
|
129
140
|
return {
|
|
130
|
-
agentId,
|
|
131
|
-
providerId
|
|
141
|
+
agentId: normalizedAgentId,
|
|
142
|
+
providerId,
|
|
132
143
|
};
|
|
133
144
|
}
|
|
134
|
-
async setAgentProvider(
|
|
135
|
-
|
|
145
|
+
async setAgentProvider(paths, agentId, providerId) {
|
|
146
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
147
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
148
|
+
await this.assertProviderExists(normalizedProviderId);
|
|
149
|
+
if (normalizedAgentId === "ceo" &&
|
|
150
|
+
normalizedProviderId !== OPENCLAW_PROVIDER_ID) {
|
|
151
|
+
throw new Error("ceo provider is fixed to \"openclaw\".");
|
|
152
|
+
}
|
|
153
|
+
const configPath = this.pathPort.join(paths.agentsDir, normalizedAgentId, AGENT_CONFIG_FILE_NAME);
|
|
154
|
+
const config = await this.readAgentConfig(configPath, normalizedAgentId);
|
|
155
|
+
const runtime = asRecord(config.runtime);
|
|
156
|
+
const currentProvider = asRecord(runtime.provider);
|
|
157
|
+
runtime.provider = {
|
|
158
|
+
...currentProvider,
|
|
159
|
+
id: normalizedProviderId,
|
|
160
|
+
};
|
|
161
|
+
if ("adapter" in runtime) {
|
|
162
|
+
delete runtime.adapter;
|
|
163
|
+
}
|
|
164
|
+
config.runtime = runtime;
|
|
165
|
+
await this.fileSystem.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
136
166
|
return {
|
|
137
|
-
agentId,
|
|
138
|
-
providerId:
|
|
167
|
+
agentId: normalizedAgentId,
|
|
168
|
+
providerId: normalizedProviderId,
|
|
139
169
|
};
|
|
140
170
|
}
|
|
141
171
|
async invokeAgent(paths, agentId, options, runtimeContext = {}) {
|
|
172
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
173
|
+
const binding = await this.getAgentProvider(paths, normalizedAgentId);
|
|
142
174
|
const registry = await this.getProviderRegistry();
|
|
143
|
-
const provider = registry.create(
|
|
175
|
+
const provider = registry.create(binding.providerId);
|
|
144
176
|
const mergedSystemPrompt = options.systemPrompt?.trim();
|
|
177
|
+
const env = await this.resolveProviderEnv(paths, provider.id, options.env);
|
|
178
|
+
const providerSessionAlias = options.providerSessionId?.trim();
|
|
179
|
+
const mappedProviderSessionId = await this.resolveProviderSessionIdAlias(paths, provider.id, normalizedAgentId, providerSessionAlias);
|
|
145
180
|
const invokeOptions = {
|
|
146
181
|
...options,
|
|
147
182
|
systemPrompt: mergedSystemPrompt || undefined,
|
|
148
183
|
cwd: options.cwd,
|
|
149
|
-
env
|
|
150
|
-
|
|
184
|
+
env,
|
|
185
|
+
providerSessionId: mappedProviderSessionId,
|
|
186
|
+
agent: provider.capabilities.agent
|
|
187
|
+
? options.agent || normalizedAgentId
|
|
188
|
+
: options.agent
|
|
151
189
|
};
|
|
152
|
-
this.logger.info("Invoking
|
|
153
|
-
agentId,
|
|
190
|
+
this.logger.info("Invoking provider for agent.", {
|
|
191
|
+
agentId: normalizedAgentId,
|
|
192
|
+
providerId: provider.id,
|
|
154
193
|
cwd: invokeOptions.cwd
|
|
155
194
|
});
|
|
156
195
|
runtimeContext.hooks?.onInvocationStarted?.({
|
|
157
196
|
runId: runtimeContext.runId,
|
|
158
197
|
timestamp: this.nowIso(),
|
|
159
198
|
step: runtimeContext.step,
|
|
160
|
-
agentId,
|
|
199
|
+
agentId: normalizedAgentId,
|
|
161
200
|
providerId: provider.id
|
|
162
201
|
});
|
|
163
|
-
let result
|
|
164
|
-
|
|
202
|
+
let result;
|
|
203
|
+
try {
|
|
204
|
+
if (provider.id === OPENCLAW_PROVIDER_ID) {
|
|
205
|
+
result = await this.invokeOpenClawProviderWithRecovery(paths, provider, invokeOptions);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
result = await provider.invoke(invokeOptions);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (error instanceof ProviderCommandNotFoundError &&
|
|
213
|
+
provider.id === OPENCLAW_PROVIDER_ID) {
|
|
214
|
+
result = await this.invokeAgentViaGateway(normalizedAgentId, invokeOptions, env);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
await this.persistProviderSessionIdAlias(paths, provider.id, normalizedAgentId, providerSessionAlias, result.providerSessionId);
|
|
165
221
|
runtimeContext.hooks?.onInvocationCompleted?.({
|
|
166
222
|
runId: runtimeContext.runId,
|
|
167
223
|
timestamp: this.nowIso(),
|
|
168
224
|
step: runtimeContext.step,
|
|
169
|
-
agentId,
|
|
225
|
+
agentId: normalizedAgentId,
|
|
170
226
|
providerId: provider.id,
|
|
171
227
|
code: result.code
|
|
172
228
|
});
|
|
173
229
|
return {
|
|
174
230
|
...result,
|
|
175
|
-
agentId,
|
|
231
|
+
agentId: normalizedAgentId,
|
|
176
232
|
providerId: provider.id
|
|
177
233
|
};
|
|
178
234
|
}
|
|
179
235
|
async createProviderAgent(paths, agentId, options) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
236
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
237
|
+
const requestedProviderId = options.providerId?.trim()
|
|
238
|
+
? normalizeProviderId(options.providerId)
|
|
239
|
+
: OPENCLAW_PROVIDER_ID;
|
|
183
240
|
const registry = await this.getProviderRegistry();
|
|
184
|
-
const provider = registry.create(
|
|
241
|
+
const provider = registry.create(requestedProviderId);
|
|
185
242
|
if (!provider.capabilities.agentCreate || !provider.createAgent) {
|
|
186
243
|
throw new UnsupportedProviderActionError(provider.id, "create_agent");
|
|
187
244
|
}
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
245
|
+
const env = await this.resolveProviderEnv(paths, provider.id, options.env);
|
|
246
|
+
let result;
|
|
247
|
+
try {
|
|
248
|
+
result = await provider.createAgent({
|
|
249
|
+
agentId: normalizedAgentId,
|
|
250
|
+
displayName: options.displayName,
|
|
251
|
+
workspaceDir: options.workspaceDir,
|
|
252
|
+
internalConfigDir: options.internalConfigDir,
|
|
253
|
+
cwd: options.cwd,
|
|
254
|
+
env,
|
|
255
|
+
onStdout: options.onStdout,
|
|
256
|
+
onStderr: options.onStderr
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (error instanceof ProviderCommandNotFoundError &&
|
|
261
|
+
provider.id === OPENCLAW_PROVIDER_ID) {
|
|
262
|
+
result = await this.createProviderAgentViaGateway(normalizedAgentId, {
|
|
263
|
+
displayName: options.displayName,
|
|
264
|
+
workspaceDir: options.workspaceDir,
|
|
265
|
+
internalConfigDir: options.internalConfigDir,
|
|
266
|
+
env,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
198
273
|
return {
|
|
199
274
|
...result,
|
|
200
|
-
agentId,
|
|
275
|
+
agentId: normalizedAgentId,
|
|
201
276
|
providerId: provider.id
|
|
202
277
|
};
|
|
203
278
|
}
|
|
204
279
|
async deleteProviderAgent(paths, agentId, options) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
280
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
281
|
+
const requestedProviderId = options.providerId?.trim()
|
|
282
|
+
? normalizeProviderId(options.providerId)
|
|
283
|
+
: OPENCLAW_PROVIDER_ID;
|
|
208
284
|
const registry = await this.getProviderRegistry();
|
|
209
|
-
const provider = registry.create(
|
|
285
|
+
const provider = registry.create(requestedProviderId);
|
|
210
286
|
if (!provider.capabilities.agentDelete || !provider.deleteAgent) {
|
|
211
287
|
throw new UnsupportedProviderActionError(provider.id, "delete_agent");
|
|
212
288
|
}
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
289
|
+
const env = await this.resolveProviderEnv(paths, provider.id, options.env);
|
|
290
|
+
let result;
|
|
291
|
+
try {
|
|
292
|
+
result = await provider.deleteAgent({
|
|
293
|
+
agentId: normalizedAgentId,
|
|
294
|
+
cwd: options.cwd,
|
|
295
|
+
env,
|
|
296
|
+
onStdout: options.onStdout,
|
|
297
|
+
onStderr: options.onStderr
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
if (error instanceof ProviderCommandNotFoundError &&
|
|
302
|
+
provider.id === OPENCLAW_PROVIDER_ID) {
|
|
303
|
+
result = await this.deleteProviderAgentViaGateway(normalizedAgentId, env);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
220
309
|
return {
|
|
221
310
|
...result,
|
|
222
|
-
agentId,
|
|
311
|
+
agentId: normalizedAgentId,
|
|
223
312
|
providerId: provider.id
|
|
224
313
|
};
|
|
225
314
|
}
|
|
226
|
-
async
|
|
315
|
+
async listOpenClawAgentsViaGateway(paths, inputEnv) {
|
|
316
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
317
|
+
const payload = await this.callGatewayMethod(env, "config.get", {});
|
|
318
|
+
const parsed = parseGatewayConfigPayload(payload);
|
|
319
|
+
if (!parsed) {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
return readGatewayAgentsList(parsed);
|
|
323
|
+
}
|
|
324
|
+
async getOpenClawSkillsStatusViaGateway(paths, inputEnv) {
|
|
325
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
326
|
+
const payload = await this.callGatewayMethod(env, "skills.status", {});
|
|
327
|
+
return asRecord(payload);
|
|
328
|
+
}
|
|
329
|
+
async syncOpenClawAgentExecutionPoliciesViaGateway(paths, rawAgentIds, inputEnv) {
|
|
330
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
331
|
+
const warnings = [];
|
|
332
|
+
const normalizedAgentIds = [
|
|
333
|
+
...new Set(rawAgentIds
|
|
334
|
+
.map((agentId) => normalizeAgentId(agentId))
|
|
335
|
+
.filter((agentId) => Boolean(agentId))),
|
|
336
|
+
];
|
|
337
|
+
if (normalizedAgentIds.length === 0) {
|
|
338
|
+
return warnings;
|
|
339
|
+
}
|
|
340
|
+
const snapshot = await this.getOpenClawConfigViaGateway(paths, env);
|
|
341
|
+
const root = snapshot.config;
|
|
342
|
+
const agents = asRecord(root.agents);
|
|
343
|
+
const list = Array.isArray(agents.list) ? [...agents.list] : [];
|
|
344
|
+
const indexById = new Map();
|
|
345
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
346
|
+
const entry = asRecord(list[index]);
|
|
347
|
+
const id = normalizeAgentId(String(entry.id ?? ""));
|
|
348
|
+
if (!id || indexById.has(id)) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
indexById.set(id, index);
|
|
352
|
+
}
|
|
353
|
+
let changed = false;
|
|
354
|
+
for (const agentId of normalizedAgentIds) {
|
|
355
|
+
const index = indexById.get(agentId);
|
|
356
|
+
if (index === undefined) {
|
|
357
|
+
warnings.push(`OpenClaw gateway policy sync skipped for "${agentId}" because no agents.list entry was found.`);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const current = asRecord(list[index]);
|
|
361
|
+
const next = {
|
|
362
|
+
...current,
|
|
363
|
+
};
|
|
364
|
+
if (readAgentSandboxMode(current) !== "off") {
|
|
365
|
+
next.sandbox = {
|
|
366
|
+
...asRecord(current.sandbox),
|
|
367
|
+
mode: "off",
|
|
368
|
+
};
|
|
369
|
+
changed = true;
|
|
370
|
+
}
|
|
371
|
+
if (!hasAgentToolsAllowAll(current)) {
|
|
372
|
+
next.tools = {
|
|
373
|
+
...asRecord(current.tools),
|
|
374
|
+
allow: ["*"],
|
|
375
|
+
};
|
|
376
|
+
changed = true;
|
|
377
|
+
}
|
|
378
|
+
list[index] = next;
|
|
379
|
+
}
|
|
380
|
+
if (!changed) {
|
|
381
|
+
return warnings;
|
|
382
|
+
}
|
|
383
|
+
const nextRoot = {
|
|
384
|
+
...root,
|
|
385
|
+
agents: {
|
|
386
|
+
...agents,
|
|
387
|
+
list,
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
await this.applyOpenClawConfigViaGateway(paths, nextRoot, snapshot.hash, env);
|
|
391
|
+
return warnings;
|
|
392
|
+
}
|
|
393
|
+
async getAgentRuntimeProfile(paths, agentId) {
|
|
394
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
395
|
+
const providerId = await this.resolveAgentProviderId(paths, normalizedAgentId);
|
|
227
396
|
const registry = await this.getProviderRegistry();
|
|
228
|
-
const provider = registry.create(
|
|
397
|
+
const provider = registry.create(providerId);
|
|
398
|
+
const runtimePolicy = registry.getProviderRuntimePolicy(provider.id);
|
|
229
399
|
return {
|
|
230
|
-
agentId,
|
|
400
|
+
agentId: normalizedAgentId,
|
|
231
401
|
providerId: provider.id,
|
|
232
402
|
providerKind: provider.kind,
|
|
233
|
-
workspaceAccess:
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
getProviderConfigPath(paths) {
|
|
237
|
-
return this.pathPort.join(paths.providersDir, OPENCLAW_PROVIDER_ID, "config.json");
|
|
238
|
-
}
|
|
239
|
-
async resolveProviderEnv(paths, inputEnv) {
|
|
240
|
-
const config = await this.getProviderConfig(paths, OPENCLAW_PROVIDER_ID);
|
|
241
|
-
return {
|
|
242
|
-
...(config?.env ?? {}),
|
|
243
|
-
...(inputEnv ?? process.env)
|
|
403
|
+
workspaceAccess: runtimePolicy.invocation.cwd,
|
|
404
|
+
roleSkillDirectories: [...runtimePolicy.skills.directories]
|
|
244
405
|
};
|
|
245
406
|
}
|
|
246
|
-
async
|
|
247
|
-
|
|
248
|
-
|
|
407
|
+
async listProviderRoleSkillDirectories() {
|
|
408
|
+
const registry = await this.getProviderRegistry();
|
|
409
|
+
const directories = new Set();
|
|
410
|
+
for (const providerId of registry.listProviderIds()) {
|
|
411
|
+
const runtimePolicy = registry.getProviderRuntimePolicy(providerId);
|
|
412
|
+
for (const directory of runtimePolicy.skills.directories) {
|
|
413
|
+
const normalized = directory.trim();
|
|
414
|
+
if (!normalized) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
directories.add(normalized);
|
|
418
|
+
}
|
|
249
419
|
}
|
|
250
|
-
|
|
420
|
+
return [...directories].sort((left, right) => left.localeCompare(right));
|
|
421
|
+
}
|
|
422
|
+
async restartLocalGateway(paths, inputEnv) {
|
|
423
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
424
|
+
const gatewayConfig = await this.getOpenClawGatewayConfig(paths, env);
|
|
251
425
|
if (gatewayConfig.mode !== "local") {
|
|
252
|
-
return
|
|
426
|
+
return false;
|
|
253
427
|
}
|
|
254
428
|
const command = gatewayConfig.command?.trim() || "openclaw";
|
|
255
429
|
const args = [
|
|
256
|
-
...extractOpenClawGlobalArgs(
|
|
430
|
+
...extractOpenClawGlobalArgs(env.OPENCLAW_ARGUMENTS),
|
|
257
431
|
"gateway",
|
|
258
432
|
"restart",
|
|
259
433
|
"--json"
|
|
@@ -262,13 +436,344 @@ export class ProviderService {
|
|
|
262
436
|
command,
|
|
263
437
|
args,
|
|
264
438
|
cwd: paths.homeDir,
|
|
265
|
-
env
|
|
439
|
+
env
|
|
266
440
|
});
|
|
267
441
|
if (restartResult.code !== 0) {
|
|
268
|
-
this.logger.warn("OpenClaw gateway restart failed
|
|
442
|
+
this.logger.warn("OpenClaw gateway restart failed.", {
|
|
269
443
|
code: restartResult.code,
|
|
270
|
-
stderr: restartResult.stderr.trim() || undefined
|
|
444
|
+
stderr: restartResult.stderr.trim() || undefined,
|
|
445
|
+
stdout: restartResult.stdout.trim() || undefined
|
|
446
|
+
});
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
this.logger.warn("OpenClaw gateway restarted.");
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
async invokeAgentViaGateway(fallbackAgentId, invokeOptions, env) {
|
|
453
|
+
const agentId = (invokeOptions.agent || fallbackAgentId).trim();
|
|
454
|
+
const payload = await this.callGatewayMethod(env, "agent", buildGatewayAgentParams({
|
|
455
|
+
message: invokeOptions.message,
|
|
456
|
+
agentId,
|
|
457
|
+
model: invokeOptions.model,
|
|
458
|
+
providerSessionId: invokeOptions.providerSessionId,
|
|
459
|
+
idempotencyKey: invokeOptions.idempotencyKey,
|
|
460
|
+
}), {
|
|
461
|
+
expectFinal: true,
|
|
462
|
+
timeoutMs: resolveGatewayAgentCallTimeoutMs(),
|
|
463
|
+
});
|
|
464
|
+
const normalized = normalizeGatewayAgentPayload(payload);
|
|
465
|
+
return {
|
|
466
|
+
code: 0,
|
|
467
|
+
stdout: normalized.stdout,
|
|
468
|
+
stderr: "",
|
|
469
|
+
providerSessionId: normalized.providerSessionId,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
async createProviderAgentViaGateway(agentId, options) {
|
|
473
|
+
const payload = await this.callGatewayMethod(options.env, "config.get", {});
|
|
474
|
+
const configPayload = asRecord(payload);
|
|
475
|
+
const rawConfig = parseGatewayConfigPayload(configPayload);
|
|
476
|
+
if (!rawConfig) {
|
|
477
|
+
throw new Error("OpenClaw gateway config.get did not return valid JSON.");
|
|
478
|
+
}
|
|
479
|
+
const root = asRecord(rawConfig);
|
|
480
|
+
const agents = asRecord(root.agents);
|
|
481
|
+
const list = Array.isArray(agents.list) ? [...agents.list] : [];
|
|
482
|
+
const normalizedTarget = normalizeAgentId(agentId);
|
|
483
|
+
if (!normalizedTarget) {
|
|
484
|
+
throw new Error("Agent id cannot be empty.");
|
|
485
|
+
}
|
|
486
|
+
const index = list.findIndex((entry) => {
|
|
487
|
+
const id = normalizeAgentId(asRecord(entry).id);
|
|
488
|
+
return id === normalizedTarget;
|
|
489
|
+
});
|
|
490
|
+
const nextEntry = {
|
|
491
|
+
...(index >= 0 ? asRecord(list[index]) : {}),
|
|
492
|
+
id: normalizedTarget,
|
|
493
|
+
name: options.displayName,
|
|
494
|
+
workspace: options.workspaceDir,
|
|
495
|
+
agentDir: options.internalConfigDir,
|
|
496
|
+
sandbox: {
|
|
497
|
+
mode: "off",
|
|
498
|
+
},
|
|
499
|
+
tools: {
|
|
500
|
+
allow: ["*"],
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
if (index >= 0) {
|
|
504
|
+
list[index] = nextEntry;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
list.push(nextEntry);
|
|
508
|
+
}
|
|
509
|
+
const nextRoot = {
|
|
510
|
+
...root,
|
|
511
|
+
agents: {
|
|
512
|
+
...agents,
|
|
513
|
+
list,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
const applyParams = {
|
|
517
|
+
raw: `${JSON.stringify(nextRoot, null, 2)}\n`,
|
|
518
|
+
};
|
|
519
|
+
const hash = configPayload.hash;
|
|
520
|
+
if (typeof hash === "string" && hash.trim().length > 0) {
|
|
521
|
+
applyParams.baseHash = hash.trim();
|
|
522
|
+
}
|
|
523
|
+
await this.callGatewayMethod(options.env, "config.apply", applyParams);
|
|
524
|
+
return {
|
|
525
|
+
code: 0,
|
|
526
|
+
stdout: "created via OpenClaw gateway config.apply",
|
|
527
|
+
stderr: "",
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
async deleteProviderAgentViaGateway(agentId, env) {
|
|
531
|
+
const normalizedTarget = normalizeAgentId(agentId);
|
|
532
|
+
if (!normalizedTarget) {
|
|
533
|
+
throw new Error("Agent id cannot be empty.");
|
|
534
|
+
}
|
|
535
|
+
const payload = await this.callGatewayMethod(env, "config.get", {});
|
|
536
|
+
const configPayload = asRecord(payload);
|
|
537
|
+
const rawConfig = parseGatewayConfigPayload(configPayload);
|
|
538
|
+
if (!rawConfig) {
|
|
539
|
+
throw new Error("OpenClaw gateway config.get did not return valid JSON.");
|
|
540
|
+
}
|
|
541
|
+
const root = asRecord(rawConfig);
|
|
542
|
+
const agents = asRecord(root.agents);
|
|
543
|
+
const list = Array.isArray(agents.list) ? [...agents.list] : [];
|
|
544
|
+
const nextList = list.filter((entry) => {
|
|
545
|
+
const id = normalizeAgentId(asRecord(entry).id);
|
|
546
|
+
return id !== normalizedTarget;
|
|
547
|
+
});
|
|
548
|
+
if (nextList.length === list.length) {
|
|
549
|
+
return {
|
|
550
|
+
code: 0,
|
|
551
|
+
stdout: "agent already absent in OpenClaw config",
|
|
552
|
+
stderr: "",
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
const nextRoot = {
|
|
556
|
+
...root,
|
|
557
|
+
agents: {
|
|
558
|
+
...agents,
|
|
559
|
+
list: nextList,
|
|
560
|
+
},
|
|
561
|
+
};
|
|
562
|
+
const applyParams = {
|
|
563
|
+
raw: `${JSON.stringify(nextRoot, null, 2)}\n`,
|
|
564
|
+
};
|
|
565
|
+
const hash = configPayload.hash;
|
|
566
|
+
if (typeof hash === "string" && hash.trim().length > 0) {
|
|
567
|
+
applyParams.baseHash = hash.trim();
|
|
568
|
+
}
|
|
569
|
+
await this.callGatewayMethod(env, "config.apply", applyParams);
|
|
570
|
+
return {
|
|
571
|
+
code: 0,
|
|
572
|
+
stdout: "deleted via OpenClaw gateway config.apply",
|
|
573
|
+
stderr: "",
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
async callGatewayMethod(env, method, params, options = {}) {
|
|
577
|
+
return callOpenClawGatewayRpc({
|
|
578
|
+
env,
|
|
579
|
+
method,
|
|
580
|
+
params,
|
|
581
|
+
options,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
async getOpenClawConfigViaGateway(paths, inputEnv) {
|
|
585
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
586
|
+
const payload = await this.callGatewayMethod(env, "config.get", {});
|
|
587
|
+
const record = asRecord(payload);
|
|
588
|
+
const config = parseGatewayConfigPayload(record);
|
|
589
|
+
if (!config) {
|
|
590
|
+
throw new Error("OpenClaw gateway config.get did not return valid JSON.");
|
|
591
|
+
}
|
|
592
|
+
const hash = record.hash;
|
|
593
|
+
return {
|
|
594
|
+
config,
|
|
595
|
+
hash: typeof hash === "string" && hash.trim().length > 0
|
|
596
|
+
? hash.trim()
|
|
597
|
+
: undefined,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
async applyOpenClawConfigViaGateway(paths, config, baseHash, inputEnv) {
|
|
601
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
602
|
+
const params = {
|
|
603
|
+
raw: `${JSON.stringify(config, null, 2)}\n`,
|
|
604
|
+
};
|
|
605
|
+
if (typeof baseHash === "string" && baseHash.trim().length > 0) {
|
|
606
|
+
params.baseHash = baseHash.trim();
|
|
607
|
+
}
|
|
608
|
+
await this.callGatewayMethod(env, "config.apply", params);
|
|
609
|
+
}
|
|
610
|
+
getProviderConfigPath(paths, providerId) {
|
|
611
|
+
return this.pathPort.join(paths.providersDir, providerId, "config.json");
|
|
612
|
+
}
|
|
613
|
+
async resolveProviderEnv(paths, providerId, inputEnv) {
|
|
614
|
+
const config = await this.getProviderConfig(paths, providerId);
|
|
615
|
+
return {
|
|
616
|
+
...(config?.env ?? {}),
|
|
617
|
+
...(inputEnv ?? process.env)
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
async resolveAgentProviderId(paths, agentId) {
|
|
621
|
+
if (agentId === "ceo") {
|
|
622
|
+
return OPENCLAW_PROVIDER_ID;
|
|
623
|
+
}
|
|
624
|
+
const configPath = this.pathPort.join(paths.agentsDir, agentId, AGENT_CONFIG_FILE_NAME);
|
|
625
|
+
const config = await this.readAgentConfig(configPath, agentId);
|
|
626
|
+
const runtime = asRecord(config.runtime);
|
|
627
|
+
const provider = asRecord(runtime.provider);
|
|
628
|
+
const explicitProviderId = readOptionalString(provider.id);
|
|
629
|
+
if (explicitProviderId) {
|
|
630
|
+
return this.assertProviderExists(explicitProviderId);
|
|
631
|
+
}
|
|
632
|
+
const legacyProviderId = readOptionalString(runtime.adapter);
|
|
633
|
+
if (legacyProviderId) {
|
|
634
|
+
return this.assertProviderExists(legacyProviderId);
|
|
635
|
+
}
|
|
636
|
+
return OPENCLAW_PROVIDER_ID;
|
|
637
|
+
}
|
|
638
|
+
async assertProviderExists(rawProviderId) {
|
|
639
|
+
const providerId = normalizeProviderId(rawProviderId);
|
|
640
|
+
const registry = await this.getProviderRegistry();
|
|
641
|
+
registry.create(providerId);
|
|
642
|
+
return providerId;
|
|
643
|
+
}
|
|
644
|
+
async readAgentConfig(configPath, agentId) {
|
|
645
|
+
const exists = await this.fileSystem.exists(configPath);
|
|
646
|
+
if (!exists) {
|
|
647
|
+
throw new AgentConfigNotFoundError(agentId);
|
|
648
|
+
}
|
|
649
|
+
const raw = await this.fileSystem.readFile(configPath);
|
|
650
|
+
let parsed;
|
|
651
|
+
try {
|
|
652
|
+
parsed = JSON.parse(raw);
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
throw new InvalidAgentConfigError(agentId, configPath);
|
|
656
|
+
}
|
|
657
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
658
|
+
throw new InvalidAgentConfigError(agentId, configPath, "schema mismatch");
|
|
659
|
+
}
|
|
660
|
+
return parsed;
|
|
661
|
+
}
|
|
662
|
+
async resolveProviderSessionIdAlias(paths, providerId, agentId, providerSessionAlias) {
|
|
663
|
+
const alias = providerSessionAlias?.trim();
|
|
664
|
+
if (!alias) {
|
|
665
|
+
return undefined;
|
|
666
|
+
}
|
|
667
|
+
if (providerId === OPENCLAW_PROVIDER_ID) {
|
|
668
|
+
return alias;
|
|
669
|
+
}
|
|
670
|
+
const bindings = await this.readProviderSessionBindings(paths, providerId, agentId);
|
|
671
|
+
const mapped = bindings.bindings[alias]?.trim();
|
|
672
|
+
return mapped || undefined;
|
|
673
|
+
}
|
|
674
|
+
async persistProviderSessionIdAlias(paths, providerId, agentId, providerSessionAlias, providerSessionId) {
|
|
675
|
+
if (providerId === OPENCLAW_PROVIDER_ID) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const alias = providerSessionAlias?.trim();
|
|
679
|
+
const resolvedProviderSessionId = providerSessionId?.trim();
|
|
680
|
+
if (!alias || !resolvedProviderSessionId) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const bindings = await this.readProviderSessionBindings(paths, providerId, agentId);
|
|
684
|
+
if (bindings.bindings[alias] === resolvedProviderSessionId) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
bindings.bindings[alias] = resolvedProviderSessionId;
|
|
688
|
+
bindings.updatedAt = this.nowIso();
|
|
689
|
+
await this.writeProviderSessionBindings(paths, providerId, agentId, bindings);
|
|
690
|
+
}
|
|
691
|
+
async readProviderSessionBindings(paths, providerId, agentId) {
|
|
692
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
693
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
694
|
+
const bindingsPath = this.getProviderSessionBindingsPath(paths, normalizedProviderId, normalizedAgentId);
|
|
695
|
+
const exists = await this.fileSystem.exists(bindingsPath);
|
|
696
|
+
if (!exists) {
|
|
697
|
+
return createProviderSessionBindingsShape({
|
|
698
|
+
providerId: normalizedProviderId,
|
|
699
|
+
agentId: normalizedAgentId,
|
|
700
|
+
updatedAt: this.nowIso(),
|
|
271
701
|
});
|
|
702
|
+
}
|
|
703
|
+
let parsed;
|
|
704
|
+
try {
|
|
705
|
+
parsed = JSON.parse(await this.fileSystem.readFile(bindingsPath));
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
return createProviderSessionBindingsShape({
|
|
709
|
+
providerId: normalizedProviderId,
|
|
710
|
+
agentId: normalizedAgentId,
|
|
711
|
+
updatedAt: this.nowIso(),
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
if (!isProviderSessionBindingsShape(parsed, normalizedProviderId, normalizedAgentId)) {
|
|
715
|
+
return createProviderSessionBindingsShape({
|
|
716
|
+
providerId: normalizedProviderId,
|
|
717
|
+
agentId: normalizedAgentId,
|
|
718
|
+
updatedAt: this.nowIso(),
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return parsed;
|
|
722
|
+
}
|
|
723
|
+
async writeProviderSessionBindings(paths, providerId, agentId, bindings) {
|
|
724
|
+
const bindingsDir = this.pathPort.join(paths.providersDir, providerId, "sessions");
|
|
725
|
+
const bindingsPath = this.getProviderSessionBindingsPath(paths, providerId, agentId);
|
|
726
|
+
await this.fileSystem.ensureDir(bindingsDir);
|
|
727
|
+
await this.fileSystem.writeFile(bindingsPath, `${JSON.stringify(bindings, null, 2)}\n`);
|
|
728
|
+
}
|
|
729
|
+
async invokeOpenClawProviderWithRecovery(paths, provider, invokeOptions) {
|
|
730
|
+
let attempt = 0;
|
|
731
|
+
while (attempt < OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS) {
|
|
732
|
+
attempt += 1;
|
|
733
|
+
let result;
|
|
734
|
+
try {
|
|
735
|
+
result = await provider.invoke(invokeOptions);
|
|
736
|
+
result = await this.retryGatewayInvocationOnUvCwdFailure(paths, provider, invokeOptions, result);
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
if (!isGatewayRetryableLockFailureError(error) || attempt >= OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS) {
|
|
740
|
+
throw error;
|
|
741
|
+
}
|
|
742
|
+
const delayMs = resolveOpenClawRetryDelayMs(attempt);
|
|
743
|
+
this.logger.warn("OpenClaw invocation failed in locked/in-flight state; retrying.", {
|
|
744
|
+
attempt,
|
|
745
|
+
maxAttempts: OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS,
|
|
746
|
+
delayMs,
|
|
747
|
+
error: summarizeRetryFailureOutput(error instanceof Error ? error.message : String(error))
|
|
748
|
+
});
|
|
749
|
+
await sleep(delayMs);
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (!isGatewayRetryableLockFailureResult(result) || attempt >= OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS) {
|
|
753
|
+
return result;
|
|
754
|
+
}
|
|
755
|
+
const delayMs = resolveOpenClawRetryDelayMs(attempt);
|
|
756
|
+
this.logger.warn("OpenClaw invocation returned locked/in-flight result; retrying.", {
|
|
757
|
+
attempt,
|
|
758
|
+
maxAttempts: OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS,
|
|
759
|
+
delayMs,
|
|
760
|
+
code: result.code,
|
|
761
|
+
stderr: summarizeRetryFailureOutput(result.stderr),
|
|
762
|
+
stdout: summarizeRetryFailureOutput(result.stdout)
|
|
763
|
+
});
|
|
764
|
+
await sleep(delayMs);
|
|
765
|
+
}
|
|
766
|
+
return provider.invoke(invokeOptions);
|
|
767
|
+
}
|
|
768
|
+
getProviderSessionBindingsPath(paths, providerId, agentId) {
|
|
769
|
+
return this.pathPort.join(paths.providersDir, providerId, "sessions", `${agentId}.json`);
|
|
770
|
+
}
|
|
771
|
+
async retryGatewayInvocationOnUvCwdFailure(paths, provider, invokeOptions, result) {
|
|
772
|
+
if (!isGatewayUvCwdFailure(result)) {
|
|
773
|
+
return result;
|
|
774
|
+
}
|
|
775
|
+
const restarted = await this.restartLocalGateway(paths, invokeOptions.env);
|
|
776
|
+
if (!restarted) {
|
|
272
777
|
return result;
|
|
273
778
|
}
|
|
274
779
|
this.logger.warn("OpenClaw gateway restarted after uv_cwd error; retrying provider invocation.");
|
|
@@ -337,6 +842,47 @@ function isGatewayUvCwdFailure(result) {
|
|
|
337
842
|
const details = `${result.stderr}\n${result.stdout}`.toLowerCase();
|
|
338
843
|
return details.includes("process.cwd failed") && details.includes("uv_cwd");
|
|
339
844
|
}
|
|
845
|
+
function isGatewayRetryableLockFailureResult(result) {
|
|
846
|
+
if (result.code === 0) {
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
return isGatewayRetryableLockFailureText(`${result.stderr}\n${result.stdout}`);
|
|
850
|
+
}
|
|
851
|
+
function isGatewayRetryableLockFailureError(error) {
|
|
852
|
+
if (!(error instanceof Error)) {
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
return isGatewayRetryableLockFailureText(error.message);
|
|
856
|
+
}
|
|
857
|
+
function isGatewayRetryableLockFailureText(value) {
|
|
858
|
+
const details = value.trim().toLowerCase();
|
|
859
|
+
if (!details) {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
return (details.includes("session file locked") ||
|
|
863
|
+
details.includes("timeout waiting for session store lock") ||
|
|
864
|
+
details.includes("in_flight") ||
|
|
865
|
+
details.includes("already in flight") ||
|
|
866
|
+
details.includes("run is already active"));
|
|
867
|
+
}
|
|
868
|
+
function resolveOpenClawRetryDelayMs(attempt) {
|
|
869
|
+
const exponent = Math.max(0, attempt - 1);
|
|
870
|
+
const next = OPENCLAW_RETRY_BACKOFF_BASE_MS * (2 ** exponent);
|
|
871
|
+
return Math.min(OPENCLAW_RETRY_BACKOFF_MAX_MS, next);
|
|
872
|
+
}
|
|
873
|
+
function summarizeRetryFailureOutput(value) {
|
|
874
|
+
const trimmed = value.trim();
|
|
875
|
+
if (!trimmed) {
|
|
876
|
+
return undefined;
|
|
877
|
+
}
|
|
878
|
+
if (trimmed.length <= 240) {
|
|
879
|
+
return trimmed;
|
|
880
|
+
}
|
|
881
|
+
return `${trimmed.slice(0, 237)}...`;
|
|
882
|
+
}
|
|
883
|
+
async function sleep(durationMs) {
|
|
884
|
+
await new Promise((resolve) => setTimeout(resolve, Math.max(0, durationMs)));
|
|
885
|
+
}
|
|
340
886
|
function extractOpenClawGlobalArgs(raw) {
|
|
341
887
|
const parts = (raw ?? "")
|
|
342
888
|
.split(/\s+/)
|
|
@@ -359,9 +905,190 @@ function extractOpenClawGlobalArgs(raw) {
|
|
|
359
905
|
}
|
|
360
906
|
return result;
|
|
361
907
|
}
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
908
|
+
function buildGatewayAgentParams(options) {
|
|
909
|
+
const idempotencyKey = options.idempotencyKey?.trim() || `opengoat-${Date.now()}-${Math.random()}`;
|
|
910
|
+
const params = {
|
|
911
|
+
message: options.message,
|
|
912
|
+
agentId: options.agentId,
|
|
913
|
+
idempotencyKey,
|
|
914
|
+
};
|
|
915
|
+
if (options.model?.trim()) {
|
|
916
|
+
params.model = options.model.trim();
|
|
917
|
+
}
|
|
918
|
+
if (options.providerSessionId?.trim()) {
|
|
919
|
+
params.sessionId = options.providerSessionId.trim();
|
|
920
|
+
params.sessionKey = buildOpenClawSessionKey(options.agentId, options.providerSessionId.trim());
|
|
921
|
+
}
|
|
922
|
+
return params;
|
|
923
|
+
}
|
|
924
|
+
function buildOpenClawSessionKey(agentId, providerSessionId) {
|
|
925
|
+
if (providerSessionId.includes(":")) {
|
|
926
|
+
return providerSessionId.toLowerCase();
|
|
927
|
+
}
|
|
928
|
+
return `agent:${normalizeSessionSegment(agentId) || "main"}:${normalizeSessionSegment(providerSessionId) || "main"}`;
|
|
929
|
+
}
|
|
930
|
+
function normalizeSessionSegment(value) {
|
|
931
|
+
return value
|
|
932
|
+
.trim()
|
|
933
|
+
.toLowerCase()
|
|
934
|
+
.replace(/[^a-z0-9:-]+/g, "-")
|
|
935
|
+
.replace(/^-+|-+$/g, "");
|
|
936
|
+
}
|
|
937
|
+
function normalizeGatewayAgentPayload(payload) {
|
|
938
|
+
const record = asRecord(payload);
|
|
939
|
+
const payloads = Array.isArray(record.payloads) ? record.payloads : [];
|
|
940
|
+
const chunks = [];
|
|
941
|
+
for (const payloadEntry of payloads) {
|
|
942
|
+
const entry = asRecord(payloadEntry);
|
|
943
|
+
const text = entry.text;
|
|
944
|
+
if (typeof text === "string" && text.trim().length > 0) {
|
|
945
|
+
chunks.push(text.trim());
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
const sessionId = asRecord(asRecord(record.meta).agentMeta).sessionId;
|
|
949
|
+
return {
|
|
950
|
+
stdout: chunks.join("\n\n").trim(),
|
|
951
|
+
providerSessionId: typeof sessionId === "string" && sessionId.trim().length > 0
|
|
952
|
+
? sessionId.trim()
|
|
953
|
+
: undefined,
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function parseGatewayConfigPayload(payload) {
|
|
957
|
+
const record = asRecord(payload);
|
|
958
|
+
const raw = record.raw;
|
|
959
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
960
|
+
return undefined;
|
|
365
961
|
}
|
|
962
|
+
const parsed = parseLooseJson(raw);
|
|
963
|
+
if (!parsed) {
|
|
964
|
+
return undefined;
|
|
965
|
+
}
|
|
966
|
+
return parsed;
|
|
967
|
+
}
|
|
968
|
+
function readGatewayAgentsList(config) {
|
|
969
|
+
const agents = asRecord(config.agents);
|
|
970
|
+
const list = Array.isArray(agents.list) ? agents.list : [];
|
|
971
|
+
const entries = [];
|
|
972
|
+
for (const entry of list) {
|
|
973
|
+
const record = asRecord(entry);
|
|
974
|
+
const id = normalizeAgentId(String(record.id ?? ""));
|
|
975
|
+
if (!id) {
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
entries.push({
|
|
979
|
+
id,
|
|
980
|
+
name: typeof record.name === "string" && record.name.trim().length > 0
|
|
981
|
+
? record.name.trim()
|
|
982
|
+
: undefined,
|
|
983
|
+
workspace: typeof record.workspace === "string" ? record.workspace : "",
|
|
984
|
+
agentDir: typeof record.agentDir === "string" ? record.agentDir : "",
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
return entries;
|
|
988
|
+
}
|
|
989
|
+
function readAgentSandboxMode(entry) {
|
|
990
|
+
const sandbox = asRecord(entry.sandbox);
|
|
991
|
+
const mode = sandbox.mode;
|
|
992
|
+
if (typeof mode !== "string") {
|
|
993
|
+
return undefined;
|
|
994
|
+
}
|
|
995
|
+
const normalized = mode.trim();
|
|
996
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
997
|
+
}
|
|
998
|
+
function hasAgentToolsAllowAll(entry) {
|
|
999
|
+
const tools = asRecord(entry.tools);
|
|
1000
|
+
const allow = tools.allow;
|
|
1001
|
+
if (!Array.isArray(allow)) {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
return allow.some((value) => typeof value === "string" && value.trim() === "*");
|
|
1005
|
+
}
|
|
1006
|
+
function parseLooseJson(raw) {
|
|
1007
|
+
const trimmed = raw.trim();
|
|
1008
|
+
if (!trimmed) {
|
|
1009
|
+
return undefined;
|
|
1010
|
+
}
|
|
1011
|
+
try {
|
|
1012
|
+
const parsed = JSON.parse(trimmed);
|
|
1013
|
+
return asRecord(parsed);
|
|
1014
|
+
}
|
|
1015
|
+
catch {
|
|
1016
|
+
// continue
|
|
1017
|
+
}
|
|
1018
|
+
const starts = [
|
|
1019
|
+
trimmed.indexOf("{"),
|
|
1020
|
+
trimmed.lastIndexOf("{"),
|
|
1021
|
+
trimmed.indexOf("["),
|
|
1022
|
+
trimmed.lastIndexOf("["),
|
|
1023
|
+
].filter((value, index, arr) => value >= 0 && arr.indexOf(value) === index);
|
|
1024
|
+
for (const start of starts) {
|
|
1025
|
+
const candidate = trimmed.slice(start).trim();
|
|
1026
|
+
if (!candidate) {
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
try {
|
|
1030
|
+
const parsed = JSON.parse(candidate);
|
|
1031
|
+
return asRecord(parsed);
|
|
1032
|
+
}
|
|
1033
|
+
catch {
|
|
1034
|
+
// keep trying
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return undefined;
|
|
1038
|
+
}
|
|
1039
|
+
function asRecord(value) {
|
|
1040
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1041
|
+
return {};
|
|
1042
|
+
}
|
|
1043
|
+
return value;
|
|
1044
|
+
}
|
|
1045
|
+
function normalizeProviderId(providerId) {
|
|
1046
|
+
const normalized = providerId.trim().toLowerCase();
|
|
1047
|
+
if (!normalized) {
|
|
1048
|
+
throw new Error("Provider id cannot be empty.");
|
|
1049
|
+
}
|
|
1050
|
+
return normalized;
|
|
1051
|
+
}
|
|
1052
|
+
function normalizeAgentIdentity(agentId) {
|
|
1053
|
+
const normalized = normalizeAgentId(agentId);
|
|
1054
|
+
if (!normalized) {
|
|
1055
|
+
throw new Error("Agent id cannot be empty.");
|
|
1056
|
+
}
|
|
1057
|
+
return normalized;
|
|
1058
|
+
}
|
|
1059
|
+
function readOptionalString(value) {
|
|
1060
|
+
if (typeof value !== "string") {
|
|
1061
|
+
return undefined;
|
|
1062
|
+
}
|
|
1063
|
+
const normalized = value.trim();
|
|
1064
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
1065
|
+
}
|
|
1066
|
+
function isProviderSessionBindingsShape(value, providerId, agentId) {
|
|
1067
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
1070
|
+
const record = value;
|
|
1071
|
+
if (record.schemaVersion !== PROVIDER_SESSION_BINDINGS_SCHEMA_VERSION) {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
if (record.providerId !== providerId || record.agentId !== agentId) {
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
if (typeof record.updatedAt !== "string" || !record.updatedAt.trim()) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
if (!record.bindings || typeof record.bindings !== "object" || Array.isArray(record.bindings)) {
|
|
1081
|
+
return false;
|
|
1082
|
+
}
|
|
1083
|
+
return true;
|
|
1084
|
+
}
|
|
1085
|
+
function createProviderSessionBindingsShape(params) {
|
|
1086
|
+
return {
|
|
1087
|
+
schemaVersion: PROVIDER_SESSION_BINDINGS_SCHEMA_VERSION,
|
|
1088
|
+
providerId: params.providerId,
|
|
1089
|
+
agentId: params.agentId,
|
|
1090
|
+
updatedAt: params.updatedAt,
|
|
1091
|
+
bindings: {},
|
|
1092
|
+
};
|
|
366
1093
|
}
|
|
367
1094
|
//# sourceMappingURL=provider.service.js.map
|