@opengoat/core 2026.2.15 → 2026.2.18-2
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 +20 -3
- package/dist/core/agents/application/agent.service.js +138 -64
- 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 +4 -1
- package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -1
- package/dist/core/opengoat/application/opengoat.service.d.ts +8 -4
- package/dist/core/opengoat/application/opengoat.service.helpers.d.ts +24 -2
- package/dist/core/opengoat/application/opengoat.service.helpers.js +145 -19
- package/dist/core/opengoat/application/opengoat.service.helpers.js.map +1 -1
- package/dist/core/opengoat/application/opengoat.service.js +285 -109
- package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
- package/dist/core/orchestration/application/orchestration.service.d.ts +1 -0
- package/dist/core/orchestration/application/orchestration.service.js +46 -29
- package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
- package/dist/core/providers/application/provider.service.d.ts +43 -4
- package/dist/core/providers/application/provider.service.js +957 -82
- 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 +15 -0
- package/dist/core/providers/provider-module.js +12 -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 +36 -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 +153 -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 +36 -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 +161 -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 +36 -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 +162 -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 +40 -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 +130 -0
- package/dist/core/providers/providers/gemini-cli/provider.js.map +1 -0
- package/dist/core/providers/providers/openclaw/index.js +12 -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 +31 -0
- package/dist/core/providers/providers/opencode/index.js.map +1 -0
- package/dist/core/providers/providers/opencode/provider.d.ts +13 -0
- package/dist/core/providers/providers/opencode/provider.js +143 -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 +66 -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 +27 -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 +3 -2
- 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 +13 -7
- package/dist/core/templates/default-templates.js.map +1 -1
- package/package.json +3 -2
|
@@ -1,7 +1,17 @@
|
|
|
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;
|
|
12
|
+
const OPENCLAW_COMMAND_RESPONSE_TIMEOUT_MS = 30_000;
|
|
13
|
+
const OPENCLAW_COMMAND_RESPONSE_POLL_MS = 250;
|
|
14
|
+
const OPENCLAW_COMMAND_HISTORY_LIMIT = 250;
|
|
5
15
|
export class ProviderService {
|
|
6
16
|
fileSystem;
|
|
7
17
|
pathPort;
|
|
@@ -18,27 +28,26 @@ export class ProviderService {
|
|
|
18
28
|
}
|
|
19
29
|
async listProviders() {
|
|
20
30
|
const registry = await this.getProviderRegistry();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
capabilities: provider.capabilities
|
|
28
|
-
}
|
|
29
|
-
];
|
|
31
|
+
return registry.listProviders().map((provider) => ({
|
|
32
|
+
id: provider.id,
|
|
33
|
+
displayName: provider.displayName,
|
|
34
|
+
kind: provider.kind,
|
|
35
|
+
capabilities: provider.capabilities
|
|
36
|
+
}));
|
|
30
37
|
}
|
|
31
38
|
async getProviderOnboarding(providerId) {
|
|
32
|
-
|
|
39
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
33
40
|
const registry = await this.getProviderRegistry();
|
|
34
|
-
return registry.getProviderOnboarding(
|
|
41
|
+
return registry.getProviderOnboarding(normalizedProviderId);
|
|
35
42
|
}
|
|
36
43
|
async invokeProviderAuth(paths, providerId, options = {}) {
|
|
37
|
-
|
|
44
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
38
45
|
const registry = await this.getProviderRegistry();
|
|
39
|
-
const provider = registry.create(
|
|
40
|
-
const env = await this.resolveProviderEnv(paths, options.env);
|
|
41
|
-
this.logger.info("Invoking
|
|
46
|
+
const provider = registry.create(normalizedProviderId);
|
|
47
|
+
const env = await this.resolveProviderEnv(paths, normalizedProviderId, options.env);
|
|
48
|
+
this.logger.info("Invoking provider auth.", {
|
|
49
|
+
providerId: provider.id
|
|
50
|
+
});
|
|
42
51
|
return provider.invokeAuth?.({
|
|
43
52
|
...options,
|
|
44
53
|
env
|
|
@@ -49,8 +58,8 @@ export class ProviderService {
|
|
|
49
58
|
};
|
|
50
59
|
}
|
|
51
60
|
async getProviderConfig(paths, providerId) {
|
|
52
|
-
|
|
53
|
-
const configPath = this.getProviderConfigPath(paths);
|
|
61
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
62
|
+
const configPath = this.getProviderConfigPath(paths, normalizedProviderId);
|
|
54
63
|
const exists = await this.fileSystem.exists(configPath);
|
|
55
64
|
if (!exists) {
|
|
56
65
|
return null;
|
|
@@ -61,18 +70,21 @@ export class ProviderService {
|
|
|
61
70
|
parsed = JSON.parse(raw);
|
|
62
71
|
}
|
|
63
72
|
catch {
|
|
64
|
-
throw new InvalidProviderConfigError(
|
|
73
|
+
throw new InvalidProviderConfigError(normalizedProviderId, configPath);
|
|
65
74
|
}
|
|
66
75
|
if (!isProviderStoredConfig(parsed)) {
|
|
67
|
-
throw new InvalidProviderConfigError(
|
|
76
|
+
throw new InvalidProviderConfigError(normalizedProviderId, configPath, "schema mismatch");
|
|
77
|
+
}
|
|
78
|
+
if (parsed.providerId.trim().toLowerCase() !== normalizedProviderId) {
|
|
79
|
+
throw new InvalidProviderConfigError(normalizedProviderId, configPath, `provider id mismatch (found "${parsed.providerId}")`);
|
|
68
80
|
}
|
|
69
81
|
return parsed;
|
|
70
82
|
}
|
|
71
83
|
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,
|
|
84
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
85
|
+
const providerDir = this.pathPort.join(paths.providersDir, normalizedProviderId);
|
|
86
|
+
const configPath = this.getProviderConfigPath(paths, normalizedProviderId);
|
|
87
|
+
const existing = await this.getProviderConfig(paths, normalizedProviderId);
|
|
76
88
|
const replace = options.replace ?? false;
|
|
77
89
|
const mergedEnv = sanitizeEnvMap({
|
|
78
90
|
...(replace ? {} : (existing?.env ?? {})),
|
|
@@ -80,7 +92,7 @@ export class ProviderService {
|
|
|
80
92
|
});
|
|
81
93
|
const next = {
|
|
82
94
|
schemaVersion: 1,
|
|
83
|
-
providerId:
|
|
95
|
+
providerId: normalizedProviderId,
|
|
84
96
|
env: mergedEnv,
|
|
85
97
|
updatedAt: this.nowIso()
|
|
86
98
|
};
|
|
@@ -89,7 +101,7 @@ export class ProviderService {
|
|
|
89
101
|
return next;
|
|
90
102
|
}
|
|
91
103
|
async getOpenClawGatewayConfig(paths, inputEnv) {
|
|
92
|
-
const env = await this.resolveProviderEnv(paths, inputEnv);
|
|
104
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
93
105
|
const explicitMode = env.OPENGOAT_OPENCLAW_GATEWAY_MODE?.trim().toLowerCase();
|
|
94
106
|
const parsedArgs = parseOpenClawArguments(env.OPENCLAW_ARGUMENTS ?? "");
|
|
95
107
|
const mode = explicitMode === "external" || parsedArgs.remoteUrl || parsedArgs.token ? "external" : "local";
|
|
@@ -125,116 +137,319 @@ export class ProviderService {
|
|
|
125
137
|
await this.setProviderConfig(paths, OPENCLAW_PROVIDER_ID, nextEnv, { replace: true });
|
|
126
138
|
return this.getOpenClawGatewayConfig(paths);
|
|
127
139
|
}
|
|
128
|
-
async getAgentProvider(
|
|
140
|
+
async getAgentProvider(paths, agentId) {
|
|
141
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
142
|
+
const providerId = await this.resolveAgentProviderId(paths, normalizedAgentId);
|
|
129
143
|
return {
|
|
130
|
-
agentId,
|
|
131
|
-
providerId
|
|
144
|
+
agentId: normalizedAgentId,
|
|
145
|
+
providerId,
|
|
132
146
|
};
|
|
133
147
|
}
|
|
134
|
-
async setAgentProvider(
|
|
135
|
-
|
|
148
|
+
async setAgentProvider(paths, agentId, providerId) {
|
|
149
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
150
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
151
|
+
await this.assertProviderExists(normalizedProviderId);
|
|
152
|
+
if (normalizedAgentId === "ceo" &&
|
|
153
|
+
normalizedProviderId !== OPENCLAW_PROVIDER_ID) {
|
|
154
|
+
throw new Error("ceo provider is fixed to \"openclaw\".");
|
|
155
|
+
}
|
|
156
|
+
const configPath = this.pathPort.join(paths.agentsDir, normalizedAgentId, AGENT_CONFIG_FILE_NAME);
|
|
157
|
+
const config = await this.readAgentConfig(configPath, normalizedAgentId);
|
|
158
|
+
const runtime = asRecord(config.runtime);
|
|
159
|
+
const currentProvider = asRecord(runtime.provider);
|
|
160
|
+
runtime.provider = {
|
|
161
|
+
...currentProvider,
|
|
162
|
+
id: normalizedProviderId,
|
|
163
|
+
};
|
|
164
|
+
if ("adapter" in runtime) {
|
|
165
|
+
delete runtime.adapter;
|
|
166
|
+
}
|
|
167
|
+
config.runtime = runtime;
|
|
168
|
+
await this.fileSystem.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
136
169
|
return {
|
|
137
|
-
agentId,
|
|
138
|
-
providerId:
|
|
170
|
+
agentId: normalizedAgentId,
|
|
171
|
+
providerId: normalizedProviderId,
|
|
139
172
|
};
|
|
140
173
|
}
|
|
141
174
|
async invokeAgent(paths, agentId, options, runtimeContext = {}) {
|
|
175
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
176
|
+
const binding = await this.getAgentProvider(paths, normalizedAgentId);
|
|
142
177
|
const registry = await this.getProviderRegistry();
|
|
143
|
-
const provider = registry.create(
|
|
178
|
+
const provider = registry.create(binding.providerId);
|
|
144
179
|
const mergedSystemPrompt = options.systemPrompt?.trim();
|
|
180
|
+
const env = await this.resolveProviderEnv(paths, provider.id, options.env);
|
|
181
|
+
const providerSessionAlias = options.providerSessionId?.trim();
|
|
182
|
+
const mappedProviderSessionId = await this.resolveProviderSessionIdAlias(paths, provider.id, normalizedAgentId, providerSessionAlias);
|
|
145
183
|
const invokeOptions = {
|
|
146
184
|
...options,
|
|
147
185
|
systemPrompt: mergedSystemPrompt || undefined,
|
|
148
186
|
cwd: options.cwd,
|
|
149
|
-
env
|
|
150
|
-
|
|
187
|
+
env,
|
|
188
|
+
providerSessionId: mappedProviderSessionId,
|
|
189
|
+
agent: provider.capabilities.agent
|
|
190
|
+
? options.agent || normalizedAgentId
|
|
191
|
+
: options.agent
|
|
151
192
|
};
|
|
152
|
-
this.logger.info("Invoking
|
|
153
|
-
agentId,
|
|
193
|
+
this.logger.info("Invoking provider for agent.", {
|
|
194
|
+
agentId: normalizedAgentId,
|
|
195
|
+
providerId: provider.id,
|
|
154
196
|
cwd: invokeOptions.cwd
|
|
155
197
|
});
|
|
156
198
|
runtimeContext.hooks?.onInvocationStarted?.({
|
|
157
199
|
runId: runtimeContext.runId,
|
|
158
200
|
timestamp: this.nowIso(),
|
|
159
201
|
step: runtimeContext.step,
|
|
160
|
-
agentId,
|
|
202
|
+
agentId: normalizedAgentId,
|
|
161
203
|
providerId: provider.id
|
|
162
204
|
});
|
|
163
|
-
let result
|
|
164
|
-
|
|
205
|
+
let result;
|
|
206
|
+
try {
|
|
207
|
+
if (provider.id === OPENCLAW_PROVIDER_ID &&
|
|
208
|
+
isOpenClawCommandMessage(invokeOptions.message)) {
|
|
209
|
+
result = await this.invokeOpenClawCommandViaGateway(normalizedAgentId, invokeOptions, env);
|
|
210
|
+
}
|
|
211
|
+
else if (provider.id === OPENCLAW_PROVIDER_ID) {
|
|
212
|
+
result = await this.invokeOpenClawProviderWithRecovery(paths, provider, invokeOptions);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
result = await provider.invoke(invokeOptions);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (error instanceof ProviderCommandNotFoundError &&
|
|
220
|
+
provider.id === OPENCLAW_PROVIDER_ID) {
|
|
221
|
+
result = await this.invokeAgentViaGateway(normalizedAgentId, invokeOptions, env);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
await this.persistProviderSessionIdAlias(paths, provider.id, normalizedAgentId, providerSessionAlias, result.providerSessionId);
|
|
165
228
|
runtimeContext.hooks?.onInvocationCompleted?.({
|
|
166
229
|
runId: runtimeContext.runId,
|
|
167
230
|
timestamp: this.nowIso(),
|
|
168
231
|
step: runtimeContext.step,
|
|
169
|
-
agentId,
|
|
232
|
+
agentId: normalizedAgentId,
|
|
170
233
|
providerId: provider.id,
|
|
171
234
|
code: result.code
|
|
172
235
|
});
|
|
173
236
|
return {
|
|
174
237
|
...result,
|
|
175
|
-
agentId,
|
|
238
|
+
agentId: normalizedAgentId,
|
|
176
239
|
providerId: provider.id
|
|
177
240
|
};
|
|
178
241
|
}
|
|
179
242
|
async createProviderAgent(paths, agentId, options) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
243
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
244
|
+
const requestedProviderId = options.providerId?.trim()
|
|
245
|
+
? normalizeProviderId(options.providerId)
|
|
246
|
+
: OPENCLAW_PROVIDER_ID;
|
|
183
247
|
const registry = await this.getProviderRegistry();
|
|
184
|
-
const provider = registry.create(
|
|
248
|
+
const provider = registry.create(requestedProviderId);
|
|
185
249
|
if (!provider.capabilities.agentCreate || !provider.createAgent) {
|
|
186
250
|
throw new UnsupportedProviderActionError(provider.id, "create_agent");
|
|
187
251
|
}
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
252
|
+
const env = await this.resolveProviderEnv(paths, provider.id, options.env);
|
|
253
|
+
let result;
|
|
254
|
+
try {
|
|
255
|
+
result = await provider.createAgent({
|
|
256
|
+
agentId: normalizedAgentId,
|
|
257
|
+
displayName: options.displayName,
|
|
258
|
+
workspaceDir: options.workspaceDir,
|
|
259
|
+
internalConfigDir: options.internalConfigDir,
|
|
260
|
+
cwd: options.cwd,
|
|
261
|
+
env,
|
|
262
|
+
onStdout: options.onStdout,
|
|
263
|
+
onStderr: options.onStderr
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
if (error instanceof ProviderCommandNotFoundError &&
|
|
268
|
+
provider.id === OPENCLAW_PROVIDER_ID) {
|
|
269
|
+
result = await this.createProviderAgentViaGateway(normalizedAgentId, {
|
|
270
|
+
displayName: options.displayName,
|
|
271
|
+
workspaceDir: options.workspaceDir,
|
|
272
|
+
internalConfigDir: options.internalConfigDir,
|
|
273
|
+
env,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
198
280
|
return {
|
|
199
281
|
...result,
|
|
200
|
-
agentId,
|
|
282
|
+
agentId: normalizedAgentId,
|
|
201
283
|
providerId: provider.id
|
|
202
284
|
};
|
|
203
285
|
}
|
|
204
286
|
async deleteProviderAgent(paths, agentId, options) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
287
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
288
|
+
const requestedProviderId = options.providerId?.trim()
|
|
289
|
+
? normalizeProviderId(options.providerId)
|
|
290
|
+
: OPENCLAW_PROVIDER_ID;
|
|
208
291
|
const registry = await this.getProviderRegistry();
|
|
209
|
-
const provider = registry.create(
|
|
292
|
+
const provider = registry.create(requestedProviderId);
|
|
210
293
|
if (!provider.capabilities.agentDelete || !provider.deleteAgent) {
|
|
211
294
|
throw new UnsupportedProviderActionError(provider.id, "delete_agent");
|
|
212
295
|
}
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
296
|
+
const env = await this.resolveProviderEnv(paths, provider.id, options.env);
|
|
297
|
+
let result;
|
|
298
|
+
try {
|
|
299
|
+
result = await provider.deleteAgent({
|
|
300
|
+
agentId: normalizedAgentId,
|
|
301
|
+
cwd: options.cwd,
|
|
302
|
+
env,
|
|
303
|
+
onStdout: options.onStdout,
|
|
304
|
+
onStderr: options.onStderr
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (error instanceof ProviderCommandNotFoundError &&
|
|
309
|
+
provider.id === OPENCLAW_PROVIDER_ID) {
|
|
310
|
+
result = await this.deleteProviderAgentViaGateway(normalizedAgentId, env);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
220
316
|
return {
|
|
221
317
|
...result,
|
|
222
|
-
agentId,
|
|
318
|
+
agentId: normalizedAgentId,
|
|
223
319
|
providerId: provider.id
|
|
224
320
|
};
|
|
225
321
|
}
|
|
226
|
-
async
|
|
322
|
+
async listOpenClawAgentsViaGateway(paths, inputEnv) {
|
|
323
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
324
|
+
const payload = await this.callGatewayMethod(env, "config.get", {});
|
|
325
|
+
const parsed = parseGatewayConfigPayload(payload);
|
|
326
|
+
if (!parsed) {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
return readGatewayAgentsList(parsed);
|
|
330
|
+
}
|
|
331
|
+
async getOpenClawSkillsStatusViaGateway(paths, inputEnv) {
|
|
332
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
333
|
+
const payload = await this.callGatewayMethod(env, "skills.status", {});
|
|
334
|
+
return asRecord(payload);
|
|
335
|
+
}
|
|
336
|
+
async syncOpenClawAgentExecutionPoliciesViaGateway(paths, rawAgentIds, inputEnv) {
|
|
337
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
338
|
+
const warnings = [];
|
|
339
|
+
const normalizedAgentIds = [
|
|
340
|
+
...new Set(rawAgentIds
|
|
341
|
+
.map((agentId) => normalizeAgentId(agentId))
|
|
342
|
+
.filter((agentId) => Boolean(agentId))),
|
|
343
|
+
];
|
|
344
|
+
if (normalizedAgentIds.length === 0) {
|
|
345
|
+
return warnings;
|
|
346
|
+
}
|
|
347
|
+
const snapshot = await this.getOpenClawConfigViaGateway(paths, env);
|
|
348
|
+
const root = snapshot.config;
|
|
349
|
+
const agents = asRecord(root.agents);
|
|
350
|
+
const list = Array.isArray(agents.list) ? [...agents.list] : [];
|
|
351
|
+
const indexById = new Map();
|
|
352
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
353
|
+
const entry = asRecord(list[index]);
|
|
354
|
+
const id = normalizeAgentId(String(entry.id ?? ""));
|
|
355
|
+
if (!id || indexById.has(id)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
indexById.set(id, index);
|
|
359
|
+
}
|
|
360
|
+
let changed = false;
|
|
361
|
+
for (const agentId of normalizedAgentIds) {
|
|
362
|
+
const index = indexById.get(agentId);
|
|
363
|
+
if (index === undefined) {
|
|
364
|
+
warnings.push(`OpenClaw gateway policy sync skipped for "${agentId}" because no agents.list entry was found.`);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const current = asRecord(list[index]);
|
|
368
|
+
const next = {
|
|
369
|
+
...current,
|
|
370
|
+
};
|
|
371
|
+
if (readAgentSandboxMode(current) !== "off") {
|
|
372
|
+
next.sandbox = {
|
|
373
|
+
...asRecord(current.sandbox),
|
|
374
|
+
mode: "off",
|
|
375
|
+
};
|
|
376
|
+
changed = true;
|
|
377
|
+
}
|
|
378
|
+
if (!hasAgentToolsAllowAll(current)) {
|
|
379
|
+
next.tools = {
|
|
380
|
+
...asRecord(current.tools),
|
|
381
|
+
allow: ["*"],
|
|
382
|
+
};
|
|
383
|
+
changed = true;
|
|
384
|
+
}
|
|
385
|
+
list[index] = next;
|
|
386
|
+
}
|
|
387
|
+
if (!changed) {
|
|
388
|
+
return warnings;
|
|
389
|
+
}
|
|
390
|
+
const nextRoot = {
|
|
391
|
+
...root,
|
|
392
|
+
agents: {
|
|
393
|
+
...agents,
|
|
394
|
+
list,
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
await this.applyOpenClawConfigViaGateway(paths, nextRoot, snapshot.hash, env);
|
|
398
|
+
return warnings;
|
|
399
|
+
}
|
|
400
|
+
async getAgentRuntimeProfile(paths, agentId) {
|
|
401
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
402
|
+
const providerId = await this.resolveAgentProviderId(paths, normalizedAgentId);
|
|
227
403
|
const registry = await this.getProviderRegistry();
|
|
228
|
-
const provider = registry.create(
|
|
404
|
+
const provider = registry.create(providerId);
|
|
405
|
+
const runtimePolicy = registry.getProviderRuntimePolicy(provider.id);
|
|
229
406
|
return {
|
|
230
|
-
agentId,
|
|
407
|
+
agentId: normalizedAgentId,
|
|
231
408
|
providerId: provider.id,
|
|
232
409
|
providerKind: provider.kind,
|
|
233
|
-
workspaceAccess:
|
|
410
|
+
workspaceAccess: runtimePolicy.invocation.cwd,
|
|
411
|
+
roleSkillDirectories: [...runtimePolicy.skills.directories],
|
|
412
|
+
roleSkillIds: {
|
|
413
|
+
manager: [...runtimePolicy.skills.roleSkillIds.manager],
|
|
414
|
+
individual: [...runtimePolicy.skills.roleSkillIds.individual],
|
|
415
|
+
},
|
|
234
416
|
};
|
|
235
417
|
}
|
|
418
|
+
async listProviderRoleSkillDirectories() {
|
|
419
|
+
const registry = await this.getProviderRegistry();
|
|
420
|
+
const directories = new Set();
|
|
421
|
+
for (const providerId of registry.listProviderIds()) {
|
|
422
|
+
const runtimePolicy = registry.getProviderRuntimePolicy(providerId);
|
|
423
|
+
for (const directory of runtimePolicy.skills.directories) {
|
|
424
|
+
const normalized = directory.trim();
|
|
425
|
+
if (!normalized) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
directories.add(normalized);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return [...directories].sort((left, right) => left.localeCompare(right));
|
|
432
|
+
}
|
|
433
|
+
async listProviderRoleSkillIds() {
|
|
434
|
+
const registry = await this.getProviderRegistry();
|
|
435
|
+
const roleSkillIds = new Set();
|
|
436
|
+
for (const providerId of registry.listProviderIds()) {
|
|
437
|
+
const runtimePolicy = registry.getProviderRuntimePolicy(providerId);
|
|
438
|
+
for (const skillId of [
|
|
439
|
+
...runtimePolicy.skills.roleSkillIds.manager,
|
|
440
|
+
...runtimePolicy.skills.roleSkillIds.individual,
|
|
441
|
+
]) {
|
|
442
|
+
const normalized = skillId.trim().toLowerCase();
|
|
443
|
+
if (!normalized) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
roleSkillIds.add(normalized);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return [...roleSkillIds].sort((left, right) => left.localeCompare(right));
|
|
450
|
+
}
|
|
236
451
|
async restartLocalGateway(paths, inputEnv) {
|
|
237
|
-
const env = await this.resolveProviderEnv(paths, inputEnv);
|
|
452
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
238
453
|
const gatewayConfig = await this.getOpenClawGatewayConfig(paths, env);
|
|
239
454
|
if (gatewayConfig.mode !== "local") {
|
|
240
455
|
return false;
|
|
@@ -263,16 +478,392 @@ export class ProviderService {
|
|
|
263
478
|
this.logger.warn("OpenClaw gateway restarted.");
|
|
264
479
|
return true;
|
|
265
480
|
}
|
|
266
|
-
|
|
267
|
-
|
|
481
|
+
async invokeAgentViaGateway(fallbackAgentId, invokeOptions, env) {
|
|
482
|
+
const agentId = (invokeOptions.agent || fallbackAgentId).trim();
|
|
483
|
+
const payload = await this.callGatewayMethod(env, "agent", buildGatewayAgentParams({
|
|
484
|
+
message: invokeOptions.message,
|
|
485
|
+
agentId,
|
|
486
|
+
model: invokeOptions.model,
|
|
487
|
+
providerSessionId: invokeOptions.providerSessionId,
|
|
488
|
+
idempotencyKey: invokeOptions.idempotencyKey,
|
|
489
|
+
}), {
|
|
490
|
+
expectFinal: true,
|
|
491
|
+
timeoutMs: resolveGatewayAgentCallTimeoutMs(),
|
|
492
|
+
});
|
|
493
|
+
const normalized = normalizeGatewayAgentPayload(payload);
|
|
494
|
+
return {
|
|
495
|
+
code: 0,
|
|
496
|
+
stdout: normalized.stdout,
|
|
497
|
+
stderr: "",
|
|
498
|
+
providerSessionId: normalized.providerSessionId,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
async invokeOpenClawCommandViaGateway(fallbackAgentId, invokeOptions, env) {
|
|
502
|
+
const agentId = (invokeOptions.agent || fallbackAgentId).trim();
|
|
503
|
+
const idempotencyKey = invokeOptions.idempotencyKey?.trim() || `opengoat-${Date.now()}-${Math.random()}`;
|
|
504
|
+
const providerSessionId = invokeOptions.providerSessionId?.trim() || idempotencyKey;
|
|
505
|
+
const sessionKey = buildOpenClawSessionKey(agentId, providerSessionId);
|
|
506
|
+
const knownSignatures = await this.captureKnownGatewayAssistantSignatures(env, sessionKey);
|
|
507
|
+
await this.callGatewayMethod(env, "chat.send", {
|
|
508
|
+
sessionKey,
|
|
509
|
+
message: invokeOptions.message,
|
|
510
|
+
idempotencyKey,
|
|
511
|
+
deliver: false,
|
|
512
|
+
}, {
|
|
513
|
+
timeoutMs: resolveGatewayAgentCallTimeoutMs(OPENCLAW_COMMAND_RESPONSE_TIMEOUT_MS),
|
|
514
|
+
});
|
|
515
|
+
const commandResponse = await this.awaitOpenClawProgrammaticCommandResponse({
|
|
516
|
+
env,
|
|
517
|
+
sessionKey,
|
|
518
|
+
knownSignatures,
|
|
519
|
+
timeoutMs: OPENCLAW_COMMAND_RESPONSE_TIMEOUT_MS,
|
|
520
|
+
pollIntervalMs: OPENCLAW_COMMAND_RESPONSE_POLL_MS,
|
|
521
|
+
});
|
|
522
|
+
return {
|
|
523
|
+
code: 0,
|
|
524
|
+
stdout: commandResponse,
|
|
525
|
+
stderr: "",
|
|
526
|
+
providerSessionId,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
async captureKnownGatewayAssistantSignatures(env, sessionKey) {
|
|
530
|
+
const messages = await this.readOpenClawChatHistoryMessages(env, sessionKey);
|
|
531
|
+
const signatures = new Set();
|
|
532
|
+
for (const message of messages) {
|
|
533
|
+
const signature = buildGatewayChatMessageSignature(message);
|
|
534
|
+
if (signature) {
|
|
535
|
+
signatures.add(signature);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return signatures;
|
|
539
|
+
}
|
|
540
|
+
async readOpenClawChatHistoryMessages(env, sessionKey) {
|
|
541
|
+
const payload = await this.callGatewayMethod(env, "chat.history", {
|
|
542
|
+
sessionKey,
|
|
543
|
+
limit: OPENCLAW_COMMAND_HISTORY_LIMIT,
|
|
544
|
+
}, {
|
|
545
|
+
timeoutMs: resolveGatewayAgentCallTimeoutMs(10_000),
|
|
546
|
+
});
|
|
547
|
+
const messages = asRecord(payload).messages;
|
|
548
|
+
if (!Array.isArray(messages)) {
|
|
549
|
+
return [];
|
|
550
|
+
}
|
|
551
|
+
return messages.map((message) => asRecord(message));
|
|
552
|
+
}
|
|
553
|
+
async awaitOpenClawProgrammaticCommandResponse(params) {
|
|
554
|
+
const deadlineMs = Date.now() + Math.max(0, params.timeoutMs);
|
|
555
|
+
while (Date.now() <= deadlineMs) {
|
|
556
|
+
const messages = await this.readOpenClawChatHistoryMessages(params.env, params.sessionKey);
|
|
557
|
+
const nextText = findNewProgrammaticAssistantText(messages, params.knownSignatures);
|
|
558
|
+
if (nextText) {
|
|
559
|
+
return nextText;
|
|
560
|
+
}
|
|
561
|
+
if (Date.now() >= deadlineMs) {
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
await sleep(params.pollIntervalMs);
|
|
565
|
+
}
|
|
566
|
+
throw new Error(`OpenClaw command timed out waiting for programmatic response for session "${params.sessionKey}".`);
|
|
567
|
+
}
|
|
568
|
+
async createProviderAgentViaGateway(agentId, options) {
|
|
569
|
+
const payload = await this.callGatewayMethod(options.env, "config.get", {});
|
|
570
|
+
const configPayload = asRecord(payload);
|
|
571
|
+
const rawConfig = parseGatewayConfigPayload(configPayload);
|
|
572
|
+
if (!rawConfig) {
|
|
573
|
+
throw new Error("OpenClaw gateway config.get did not return valid JSON.");
|
|
574
|
+
}
|
|
575
|
+
const root = asRecord(rawConfig);
|
|
576
|
+
const agents = asRecord(root.agents);
|
|
577
|
+
const list = Array.isArray(agents.list) ? [...agents.list] : [];
|
|
578
|
+
const normalizedTarget = normalizeAgentId(agentId);
|
|
579
|
+
if (!normalizedTarget) {
|
|
580
|
+
throw new Error("Agent id cannot be empty.");
|
|
581
|
+
}
|
|
582
|
+
const index = list.findIndex((entry) => {
|
|
583
|
+
const id = normalizeAgentId(asRecord(entry).id);
|
|
584
|
+
return id === normalizedTarget;
|
|
585
|
+
});
|
|
586
|
+
const nextEntry = {
|
|
587
|
+
...(index >= 0 ? asRecord(list[index]) : {}),
|
|
588
|
+
id: normalizedTarget,
|
|
589
|
+
name: options.displayName,
|
|
590
|
+
workspace: options.workspaceDir,
|
|
591
|
+
agentDir: options.internalConfigDir,
|
|
592
|
+
sandbox: {
|
|
593
|
+
mode: "off",
|
|
594
|
+
},
|
|
595
|
+
tools: {
|
|
596
|
+
allow: ["*"],
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
if (index >= 0) {
|
|
600
|
+
list[index] = nextEntry;
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
list.push(nextEntry);
|
|
604
|
+
}
|
|
605
|
+
const nextRoot = {
|
|
606
|
+
...root,
|
|
607
|
+
agents: {
|
|
608
|
+
...agents,
|
|
609
|
+
list,
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
const applyParams = {
|
|
613
|
+
raw: `${JSON.stringify(nextRoot, null, 2)}\n`,
|
|
614
|
+
};
|
|
615
|
+
const hash = configPayload.hash;
|
|
616
|
+
if (typeof hash === "string" && hash.trim().length > 0) {
|
|
617
|
+
applyParams.baseHash = hash.trim();
|
|
618
|
+
}
|
|
619
|
+
await this.callGatewayMethod(options.env, "config.apply", applyParams);
|
|
620
|
+
return {
|
|
621
|
+
code: 0,
|
|
622
|
+
stdout: "created via OpenClaw gateway config.apply",
|
|
623
|
+
stderr: "",
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
async deleteProviderAgentViaGateway(agentId, env) {
|
|
627
|
+
const normalizedTarget = normalizeAgentId(agentId);
|
|
628
|
+
if (!normalizedTarget) {
|
|
629
|
+
throw new Error("Agent id cannot be empty.");
|
|
630
|
+
}
|
|
631
|
+
const payload = await this.callGatewayMethod(env, "config.get", {});
|
|
632
|
+
const configPayload = asRecord(payload);
|
|
633
|
+
const rawConfig = parseGatewayConfigPayload(configPayload);
|
|
634
|
+
if (!rawConfig) {
|
|
635
|
+
throw new Error("OpenClaw gateway config.get did not return valid JSON.");
|
|
636
|
+
}
|
|
637
|
+
const root = asRecord(rawConfig);
|
|
638
|
+
const agents = asRecord(root.agents);
|
|
639
|
+
const list = Array.isArray(agents.list) ? [...agents.list] : [];
|
|
640
|
+
const nextList = list.filter((entry) => {
|
|
641
|
+
const id = normalizeAgentId(asRecord(entry).id);
|
|
642
|
+
return id !== normalizedTarget;
|
|
643
|
+
});
|
|
644
|
+
if (nextList.length === list.length) {
|
|
645
|
+
return {
|
|
646
|
+
code: 0,
|
|
647
|
+
stdout: "agent already absent in OpenClaw config",
|
|
648
|
+
stderr: "",
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const nextRoot = {
|
|
652
|
+
...root,
|
|
653
|
+
agents: {
|
|
654
|
+
...agents,
|
|
655
|
+
list: nextList,
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
const applyParams = {
|
|
659
|
+
raw: `${JSON.stringify(nextRoot, null, 2)}\n`,
|
|
660
|
+
};
|
|
661
|
+
const hash = configPayload.hash;
|
|
662
|
+
if (typeof hash === "string" && hash.trim().length > 0) {
|
|
663
|
+
applyParams.baseHash = hash.trim();
|
|
664
|
+
}
|
|
665
|
+
await this.callGatewayMethod(env, "config.apply", applyParams);
|
|
666
|
+
return {
|
|
667
|
+
code: 0,
|
|
668
|
+
stdout: "deleted via OpenClaw gateway config.apply",
|
|
669
|
+
stderr: "",
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
async callGatewayMethod(env, method, params, options = {}) {
|
|
673
|
+
return callOpenClawGatewayRpc({
|
|
674
|
+
env,
|
|
675
|
+
method,
|
|
676
|
+
params,
|
|
677
|
+
options,
|
|
678
|
+
});
|
|
268
679
|
}
|
|
269
|
-
async
|
|
270
|
-
const
|
|
680
|
+
async getOpenClawConfigViaGateway(paths, inputEnv) {
|
|
681
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
682
|
+
const payload = await this.callGatewayMethod(env, "config.get", {});
|
|
683
|
+
const record = asRecord(payload);
|
|
684
|
+
const config = parseGatewayConfigPayload(record);
|
|
685
|
+
if (!config) {
|
|
686
|
+
throw new Error("OpenClaw gateway config.get did not return valid JSON.");
|
|
687
|
+
}
|
|
688
|
+
const hash = record.hash;
|
|
689
|
+
return {
|
|
690
|
+
config,
|
|
691
|
+
hash: typeof hash === "string" && hash.trim().length > 0
|
|
692
|
+
? hash.trim()
|
|
693
|
+
: undefined,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
async applyOpenClawConfigViaGateway(paths, config, baseHash, inputEnv) {
|
|
697
|
+
const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
|
|
698
|
+
const params = {
|
|
699
|
+
raw: `${JSON.stringify(config, null, 2)}\n`,
|
|
700
|
+
};
|
|
701
|
+
if (typeof baseHash === "string" && baseHash.trim().length > 0) {
|
|
702
|
+
params.baseHash = baseHash.trim();
|
|
703
|
+
}
|
|
704
|
+
await this.callGatewayMethod(env, "config.apply", params);
|
|
705
|
+
}
|
|
706
|
+
getProviderConfigPath(paths, providerId) {
|
|
707
|
+
return this.pathPort.join(paths.providersDir, providerId, "config.json");
|
|
708
|
+
}
|
|
709
|
+
async resolveProviderEnv(paths, providerId, inputEnv) {
|
|
710
|
+
const config = await this.getProviderConfig(paths, providerId);
|
|
271
711
|
return {
|
|
272
712
|
...(config?.env ?? {}),
|
|
273
713
|
...(inputEnv ?? process.env)
|
|
274
714
|
};
|
|
275
715
|
}
|
|
716
|
+
async resolveAgentProviderId(paths, agentId) {
|
|
717
|
+
if (agentId === "ceo") {
|
|
718
|
+
return OPENCLAW_PROVIDER_ID;
|
|
719
|
+
}
|
|
720
|
+
const configPath = this.pathPort.join(paths.agentsDir, agentId, AGENT_CONFIG_FILE_NAME);
|
|
721
|
+
const config = await this.readAgentConfig(configPath, agentId);
|
|
722
|
+
const runtime = asRecord(config.runtime);
|
|
723
|
+
const provider = asRecord(runtime.provider);
|
|
724
|
+
const explicitProviderId = readOptionalString(provider.id);
|
|
725
|
+
if (explicitProviderId) {
|
|
726
|
+
return this.assertProviderExists(explicitProviderId);
|
|
727
|
+
}
|
|
728
|
+
const legacyProviderId = readOptionalString(runtime.adapter);
|
|
729
|
+
if (legacyProviderId) {
|
|
730
|
+
return this.assertProviderExists(legacyProviderId);
|
|
731
|
+
}
|
|
732
|
+
return OPENCLAW_PROVIDER_ID;
|
|
733
|
+
}
|
|
734
|
+
async assertProviderExists(rawProviderId) {
|
|
735
|
+
const providerId = normalizeProviderId(rawProviderId);
|
|
736
|
+
const registry = await this.getProviderRegistry();
|
|
737
|
+
registry.create(providerId);
|
|
738
|
+
return providerId;
|
|
739
|
+
}
|
|
740
|
+
async readAgentConfig(configPath, agentId) {
|
|
741
|
+
const exists = await this.fileSystem.exists(configPath);
|
|
742
|
+
if (!exists) {
|
|
743
|
+
throw new AgentConfigNotFoundError(agentId);
|
|
744
|
+
}
|
|
745
|
+
const raw = await this.fileSystem.readFile(configPath);
|
|
746
|
+
let parsed;
|
|
747
|
+
try {
|
|
748
|
+
parsed = JSON.parse(raw);
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
throw new InvalidAgentConfigError(agentId, configPath);
|
|
752
|
+
}
|
|
753
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
754
|
+
throw new InvalidAgentConfigError(agentId, configPath, "schema mismatch");
|
|
755
|
+
}
|
|
756
|
+
return parsed;
|
|
757
|
+
}
|
|
758
|
+
async resolveProviderSessionIdAlias(paths, providerId, agentId, providerSessionAlias) {
|
|
759
|
+
const alias = providerSessionAlias?.trim();
|
|
760
|
+
if (!alias) {
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
if (providerId === OPENCLAW_PROVIDER_ID) {
|
|
764
|
+
return alias;
|
|
765
|
+
}
|
|
766
|
+
const bindings = await this.readProviderSessionBindings(paths, providerId, agentId);
|
|
767
|
+
const mapped = bindings.bindings[alias]?.trim();
|
|
768
|
+
return mapped || undefined;
|
|
769
|
+
}
|
|
770
|
+
async persistProviderSessionIdAlias(paths, providerId, agentId, providerSessionAlias, providerSessionId) {
|
|
771
|
+
if (providerId === OPENCLAW_PROVIDER_ID) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const alias = providerSessionAlias?.trim();
|
|
775
|
+
const resolvedProviderSessionId = providerSessionId?.trim();
|
|
776
|
+
if (!alias || !resolvedProviderSessionId) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const bindings = await this.readProviderSessionBindings(paths, providerId, agentId);
|
|
780
|
+
if (bindings.bindings[alias] === resolvedProviderSessionId) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
bindings.bindings[alias] = resolvedProviderSessionId;
|
|
784
|
+
bindings.updatedAt = this.nowIso();
|
|
785
|
+
await this.writeProviderSessionBindings(paths, providerId, agentId, bindings);
|
|
786
|
+
}
|
|
787
|
+
async readProviderSessionBindings(paths, providerId, agentId) {
|
|
788
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
789
|
+
const normalizedAgentId = normalizeAgentIdentity(agentId);
|
|
790
|
+
const bindingsPath = this.getProviderSessionBindingsPath(paths, normalizedProviderId, normalizedAgentId);
|
|
791
|
+
const exists = await this.fileSystem.exists(bindingsPath);
|
|
792
|
+
if (!exists) {
|
|
793
|
+
return createProviderSessionBindingsShape({
|
|
794
|
+
providerId: normalizedProviderId,
|
|
795
|
+
agentId: normalizedAgentId,
|
|
796
|
+
updatedAt: this.nowIso(),
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
let parsed;
|
|
800
|
+
try {
|
|
801
|
+
parsed = JSON.parse(await this.fileSystem.readFile(bindingsPath));
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
return createProviderSessionBindingsShape({
|
|
805
|
+
providerId: normalizedProviderId,
|
|
806
|
+
agentId: normalizedAgentId,
|
|
807
|
+
updatedAt: this.nowIso(),
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
if (!isProviderSessionBindingsShape(parsed, normalizedProviderId, normalizedAgentId)) {
|
|
811
|
+
return createProviderSessionBindingsShape({
|
|
812
|
+
providerId: normalizedProviderId,
|
|
813
|
+
agentId: normalizedAgentId,
|
|
814
|
+
updatedAt: this.nowIso(),
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
return parsed;
|
|
818
|
+
}
|
|
819
|
+
async writeProviderSessionBindings(paths, providerId, agentId, bindings) {
|
|
820
|
+
const bindingsDir = this.pathPort.join(paths.providersDir, providerId, "sessions");
|
|
821
|
+
const bindingsPath = this.getProviderSessionBindingsPath(paths, providerId, agentId);
|
|
822
|
+
await this.fileSystem.ensureDir(bindingsDir);
|
|
823
|
+
await this.fileSystem.writeFile(bindingsPath, `${JSON.stringify(bindings, null, 2)}\n`);
|
|
824
|
+
}
|
|
825
|
+
async invokeOpenClawProviderWithRecovery(paths, provider, invokeOptions) {
|
|
826
|
+
let attempt = 0;
|
|
827
|
+
while (attempt < OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS) {
|
|
828
|
+
attempt += 1;
|
|
829
|
+
let result;
|
|
830
|
+
try {
|
|
831
|
+
result = await provider.invoke(invokeOptions);
|
|
832
|
+
result = await this.retryGatewayInvocationOnUvCwdFailure(paths, provider, invokeOptions, result);
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
if (!isGatewayRetryableLockFailureError(error) || attempt >= OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS) {
|
|
836
|
+
throw error;
|
|
837
|
+
}
|
|
838
|
+
const delayMs = resolveOpenClawRetryDelayMs(attempt);
|
|
839
|
+
this.logger.warn("OpenClaw invocation failed in locked/in-flight state; retrying.", {
|
|
840
|
+
attempt,
|
|
841
|
+
maxAttempts: OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS,
|
|
842
|
+
delayMs,
|
|
843
|
+
error: summarizeRetryFailureOutput(error instanceof Error ? error.message : String(error))
|
|
844
|
+
});
|
|
845
|
+
await sleep(delayMs);
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (!isGatewayRetryableLockFailureResult(result) || attempt >= OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS) {
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
const delayMs = resolveOpenClawRetryDelayMs(attempt);
|
|
852
|
+
this.logger.warn("OpenClaw invocation returned locked/in-flight result; retrying.", {
|
|
853
|
+
attempt,
|
|
854
|
+
maxAttempts: OPENCLAW_RETRYABLE_FAILURE_MAX_ATTEMPTS,
|
|
855
|
+
delayMs,
|
|
856
|
+
code: result.code,
|
|
857
|
+
stderr: summarizeRetryFailureOutput(result.stderr),
|
|
858
|
+
stdout: summarizeRetryFailureOutput(result.stdout)
|
|
859
|
+
});
|
|
860
|
+
await sleep(delayMs);
|
|
861
|
+
}
|
|
862
|
+
return provider.invoke(invokeOptions);
|
|
863
|
+
}
|
|
864
|
+
getProviderSessionBindingsPath(paths, providerId, agentId) {
|
|
865
|
+
return this.pathPort.join(paths.providersDir, providerId, "sessions", `${agentId}.json`);
|
|
866
|
+
}
|
|
276
867
|
async retryGatewayInvocationOnUvCwdFailure(paths, provider, invokeOptions, result) {
|
|
277
868
|
if (!isGatewayUvCwdFailure(result)) {
|
|
278
869
|
return result;
|
|
@@ -347,6 +938,47 @@ function isGatewayUvCwdFailure(result) {
|
|
|
347
938
|
const details = `${result.stderr}\n${result.stdout}`.toLowerCase();
|
|
348
939
|
return details.includes("process.cwd failed") && details.includes("uv_cwd");
|
|
349
940
|
}
|
|
941
|
+
function isGatewayRetryableLockFailureResult(result) {
|
|
942
|
+
if (result.code === 0) {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
return isGatewayRetryableLockFailureText(`${result.stderr}\n${result.stdout}`);
|
|
946
|
+
}
|
|
947
|
+
function isGatewayRetryableLockFailureError(error) {
|
|
948
|
+
if (!(error instanceof Error)) {
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
return isGatewayRetryableLockFailureText(error.message);
|
|
952
|
+
}
|
|
953
|
+
function isGatewayRetryableLockFailureText(value) {
|
|
954
|
+
const details = value.trim().toLowerCase();
|
|
955
|
+
if (!details) {
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
return (details.includes("session file locked") ||
|
|
959
|
+
details.includes("timeout waiting for session store lock") ||
|
|
960
|
+
details.includes("in_flight") ||
|
|
961
|
+
details.includes("already in flight") ||
|
|
962
|
+
details.includes("run is already active"));
|
|
963
|
+
}
|
|
964
|
+
function resolveOpenClawRetryDelayMs(attempt) {
|
|
965
|
+
const exponent = Math.max(0, attempt - 1);
|
|
966
|
+
const next = OPENCLAW_RETRY_BACKOFF_BASE_MS * (2 ** exponent);
|
|
967
|
+
return Math.min(OPENCLAW_RETRY_BACKOFF_MAX_MS, next);
|
|
968
|
+
}
|
|
969
|
+
function summarizeRetryFailureOutput(value) {
|
|
970
|
+
const trimmed = value.trim();
|
|
971
|
+
if (!trimmed) {
|
|
972
|
+
return undefined;
|
|
973
|
+
}
|
|
974
|
+
if (trimmed.length <= 240) {
|
|
975
|
+
return trimmed;
|
|
976
|
+
}
|
|
977
|
+
return `${trimmed.slice(0, 237)}...`;
|
|
978
|
+
}
|
|
979
|
+
async function sleep(durationMs) {
|
|
980
|
+
await new Promise((resolve) => setTimeout(resolve, Math.max(0, durationMs)));
|
|
981
|
+
}
|
|
350
982
|
function extractOpenClawGlobalArgs(raw) {
|
|
351
983
|
const parts = (raw ?? "")
|
|
352
984
|
.split(/\s+/)
|
|
@@ -369,9 +1001,252 @@ function extractOpenClawGlobalArgs(raw) {
|
|
|
369
1001
|
}
|
|
370
1002
|
return result;
|
|
371
1003
|
}
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
|
|
1004
|
+
function buildGatewayAgentParams(options) {
|
|
1005
|
+
const idempotencyKey = options.idempotencyKey?.trim() || `opengoat-${Date.now()}-${Math.random()}`;
|
|
1006
|
+
const params = {
|
|
1007
|
+
message: options.message,
|
|
1008
|
+
agentId: options.agentId,
|
|
1009
|
+
idempotencyKey,
|
|
1010
|
+
};
|
|
1011
|
+
if (options.model?.trim()) {
|
|
1012
|
+
params.model = options.model.trim();
|
|
1013
|
+
}
|
|
1014
|
+
if (options.providerSessionId?.trim()) {
|
|
1015
|
+
params.sessionId = options.providerSessionId.trim();
|
|
1016
|
+
params.sessionKey = buildOpenClawSessionKey(options.agentId, options.providerSessionId.trim());
|
|
1017
|
+
}
|
|
1018
|
+
return params;
|
|
1019
|
+
}
|
|
1020
|
+
function buildOpenClawSessionKey(agentId, providerSessionId) {
|
|
1021
|
+
if (providerSessionId.includes(":")) {
|
|
1022
|
+
return providerSessionId.toLowerCase();
|
|
1023
|
+
}
|
|
1024
|
+
return `agent:${normalizeSessionSegment(agentId) || "main"}:${normalizeSessionSegment(providerSessionId) || "main"}`;
|
|
1025
|
+
}
|
|
1026
|
+
function normalizeSessionSegment(value) {
|
|
1027
|
+
return value
|
|
1028
|
+
.trim()
|
|
1029
|
+
.toLowerCase()
|
|
1030
|
+
.replace(/[^a-z0-9:-]+/g, "-")
|
|
1031
|
+
.replace(/^-+|-+$/g, "");
|
|
1032
|
+
}
|
|
1033
|
+
function isOpenClawCommandMessage(message) {
|
|
1034
|
+
return message.trim().startsWith("/");
|
|
1035
|
+
}
|
|
1036
|
+
function normalizeGatewayAgentPayload(payload) {
|
|
1037
|
+
const record = asRecord(payload);
|
|
1038
|
+
const payloads = Array.isArray(record.payloads) ? record.payloads : [];
|
|
1039
|
+
const chunks = [];
|
|
1040
|
+
for (const payloadEntry of payloads) {
|
|
1041
|
+
const entry = asRecord(payloadEntry);
|
|
1042
|
+
const text = entry.text;
|
|
1043
|
+
if (typeof text === "string" && text.trim().length > 0) {
|
|
1044
|
+
chunks.push(text.trim());
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
const sessionId = asRecord(asRecord(record.meta).agentMeta).sessionId;
|
|
1048
|
+
return {
|
|
1049
|
+
stdout: chunks.join("\n\n").trim(),
|
|
1050
|
+
providerSessionId: typeof sessionId === "string" && sessionId.trim().length > 0
|
|
1051
|
+
? sessionId.trim()
|
|
1052
|
+
: undefined,
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function buildGatewayChatMessageSignature(message) {
|
|
1056
|
+
const role = readOptionalString(message.role);
|
|
1057
|
+
const timestamp = typeof message.timestamp === "number" ? String(message.timestamp) : "";
|
|
1058
|
+
const text = extractGatewayChatText(message);
|
|
1059
|
+
if (!role || !timestamp || !text) {
|
|
1060
|
+
return undefined;
|
|
1061
|
+
}
|
|
1062
|
+
return `${role}|${timestamp}|${text}`;
|
|
1063
|
+
}
|
|
1064
|
+
function findNewProgrammaticAssistantText(messages, knownSignatures) {
|
|
1065
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1066
|
+
const message = messages[index];
|
|
1067
|
+
if (!message || !isProgrammaticGatewayAssistantMessage(message)) {
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
const signature = buildGatewayChatMessageSignature(message);
|
|
1071
|
+
if (!signature || knownSignatures.has(signature)) {
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
const text = extractGatewayChatText(message);
|
|
1075
|
+
if (!text) {
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
knownSignatures.add(signature);
|
|
1079
|
+
return text;
|
|
1080
|
+
}
|
|
1081
|
+
return undefined;
|
|
1082
|
+
}
|
|
1083
|
+
function isProgrammaticGatewayAssistantMessage(message) {
|
|
1084
|
+
if (readOptionalString(message.role) !== "assistant") {
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
const provider = readOptionalString(message.provider);
|
|
1088
|
+
const model = readOptionalString(message.model);
|
|
1089
|
+
if (provider === "openclaw" && model === "gateway-injected") {
|
|
1090
|
+
return true;
|
|
1091
|
+
}
|
|
1092
|
+
const usage = asRecord(message.usage);
|
|
1093
|
+
const totalTokens = usage.totalTokens;
|
|
1094
|
+
return typeof totalTokens === "number" && totalTokens === 0;
|
|
1095
|
+
}
|
|
1096
|
+
function extractGatewayChatText(message) {
|
|
1097
|
+
const content = message.content;
|
|
1098
|
+
if (!Array.isArray(content)) {
|
|
1099
|
+
return "";
|
|
1100
|
+
}
|
|
1101
|
+
const lines = [];
|
|
1102
|
+
for (const chunk of content) {
|
|
1103
|
+
const record = asRecord(chunk);
|
|
1104
|
+
if (readOptionalString(record.type) !== "text") {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
const text = readOptionalString(record.text);
|
|
1108
|
+
if (text) {
|
|
1109
|
+
lines.push(text);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return lines.join("\n\n").trim();
|
|
1113
|
+
}
|
|
1114
|
+
function parseGatewayConfigPayload(payload) {
|
|
1115
|
+
const record = asRecord(payload);
|
|
1116
|
+
const raw = record.raw;
|
|
1117
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
1118
|
+
return undefined;
|
|
1119
|
+
}
|
|
1120
|
+
const parsed = parseLooseJson(raw);
|
|
1121
|
+
if (!parsed) {
|
|
1122
|
+
return undefined;
|
|
1123
|
+
}
|
|
1124
|
+
return parsed;
|
|
1125
|
+
}
|
|
1126
|
+
function readGatewayAgentsList(config) {
|
|
1127
|
+
const agents = asRecord(config.agents);
|
|
1128
|
+
const list = Array.isArray(agents.list) ? agents.list : [];
|
|
1129
|
+
const entries = [];
|
|
1130
|
+
for (const entry of list) {
|
|
1131
|
+
const record = asRecord(entry);
|
|
1132
|
+
const id = normalizeAgentId(String(record.id ?? ""));
|
|
1133
|
+
if (!id) {
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
entries.push({
|
|
1137
|
+
id,
|
|
1138
|
+
name: typeof record.name === "string" && record.name.trim().length > 0
|
|
1139
|
+
? record.name.trim()
|
|
1140
|
+
: undefined,
|
|
1141
|
+
workspace: typeof record.workspace === "string" ? record.workspace : "",
|
|
1142
|
+
agentDir: typeof record.agentDir === "string" ? record.agentDir : "",
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
return entries;
|
|
1146
|
+
}
|
|
1147
|
+
function readAgentSandboxMode(entry) {
|
|
1148
|
+
const sandbox = asRecord(entry.sandbox);
|
|
1149
|
+
const mode = sandbox.mode;
|
|
1150
|
+
if (typeof mode !== "string") {
|
|
1151
|
+
return undefined;
|
|
1152
|
+
}
|
|
1153
|
+
const normalized = mode.trim();
|
|
1154
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
1155
|
+
}
|
|
1156
|
+
function hasAgentToolsAllowAll(entry) {
|
|
1157
|
+
const tools = asRecord(entry.tools);
|
|
1158
|
+
const allow = tools.allow;
|
|
1159
|
+
if (!Array.isArray(allow)) {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
return allow.some((value) => typeof value === "string" && value.trim() === "*");
|
|
1163
|
+
}
|
|
1164
|
+
function parseLooseJson(raw) {
|
|
1165
|
+
const trimmed = raw.trim();
|
|
1166
|
+
if (!trimmed) {
|
|
1167
|
+
return undefined;
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
const parsed = JSON.parse(trimmed);
|
|
1171
|
+
return asRecord(parsed);
|
|
375
1172
|
}
|
|
1173
|
+
catch {
|
|
1174
|
+
// continue
|
|
1175
|
+
}
|
|
1176
|
+
const starts = [
|
|
1177
|
+
trimmed.indexOf("{"),
|
|
1178
|
+
trimmed.lastIndexOf("{"),
|
|
1179
|
+
trimmed.indexOf("["),
|
|
1180
|
+
trimmed.lastIndexOf("["),
|
|
1181
|
+
].filter((value, index, arr) => value >= 0 && arr.indexOf(value) === index);
|
|
1182
|
+
for (const start of starts) {
|
|
1183
|
+
const candidate = trimmed.slice(start).trim();
|
|
1184
|
+
if (!candidate) {
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
try {
|
|
1188
|
+
const parsed = JSON.parse(candidate);
|
|
1189
|
+
return asRecord(parsed);
|
|
1190
|
+
}
|
|
1191
|
+
catch {
|
|
1192
|
+
// keep trying
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return undefined;
|
|
1196
|
+
}
|
|
1197
|
+
function asRecord(value) {
|
|
1198
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1199
|
+
return {};
|
|
1200
|
+
}
|
|
1201
|
+
return value;
|
|
1202
|
+
}
|
|
1203
|
+
function normalizeProviderId(providerId) {
|
|
1204
|
+
const normalized = providerId.trim().toLowerCase();
|
|
1205
|
+
if (!normalized) {
|
|
1206
|
+
throw new Error("Provider id cannot be empty.");
|
|
1207
|
+
}
|
|
1208
|
+
return normalized;
|
|
1209
|
+
}
|
|
1210
|
+
function normalizeAgentIdentity(agentId) {
|
|
1211
|
+
const normalized = normalizeAgentId(agentId);
|
|
1212
|
+
if (!normalized) {
|
|
1213
|
+
throw new Error("Agent id cannot be empty.");
|
|
1214
|
+
}
|
|
1215
|
+
return normalized;
|
|
1216
|
+
}
|
|
1217
|
+
function readOptionalString(value) {
|
|
1218
|
+
if (typeof value !== "string") {
|
|
1219
|
+
return undefined;
|
|
1220
|
+
}
|
|
1221
|
+
const normalized = value.trim();
|
|
1222
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
1223
|
+
}
|
|
1224
|
+
function isProviderSessionBindingsShape(value, providerId, agentId) {
|
|
1225
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1228
|
+
const record = value;
|
|
1229
|
+
if (record.schemaVersion !== PROVIDER_SESSION_BINDINGS_SCHEMA_VERSION) {
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
if (record.providerId !== providerId || record.agentId !== agentId) {
|
|
1233
|
+
return false;
|
|
1234
|
+
}
|
|
1235
|
+
if (typeof record.updatedAt !== "string" || !record.updatedAt.trim()) {
|
|
1236
|
+
return false;
|
|
1237
|
+
}
|
|
1238
|
+
if (!record.bindings || typeof record.bindings !== "object" || Array.isArray(record.bindings)) {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1243
|
+
function createProviderSessionBindingsShape(params) {
|
|
1244
|
+
return {
|
|
1245
|
+
schemaVersion: PROVIDER_SESSION_BINDINGS_SCHEMA_VERSION,
|
|
1246
|
+
providerId: params.providerId,
|
|
1247
|
+
agentId: params.agentId,
|
|
1248
|
+
updatedAt: params.updatedAt,
|
|
1249
|
+
bindings: {},
|
|
1250
|
+
};
|
|
376
1251
|
}
|
|
377
1252
|
//# sourceMappingURL=provider.service.js.map
|