@opengoat/core 2026.2.15 → 2026.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/core/agents/application/agent.service.d.ts +12 -3
  2. package/dist/core/agents/application/agent.service.js +98 -48
  3. package/dist/core/agents/application/agent.service.js.map +1 -1
  4. package/dist/core/boards/application/board.service.d.ts +2 -0
  5. package/dist/core/boards/application/board.service.js +36 -2
  6. package/dist/core/boards/application/board.service.js.map +1 -1
  7. package/dist/core/bootstrap/application/bootstrap.service.js +4 -1
  8. package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -1
  9. package/dist/core/opengoat/application/opengoat.service.d.ts +5 -3
  10. package/dist/core/opengoat/application/opengoat.service.helpers.d.ts +12 -2
  11. package/dist/core/opengoat/application/opengoat.service.helpers.js +86 -8
  12. package/dist/core/opengoat/application/opengoat.service.helpers.js.map +1 -1
  13. package/dist/core/opengoat/application/opengoat.service.js +238 -89
  14. package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
  15. package/dist/core/orchestration/application/orchestration.service.d.ts +1 -0
  16. package/dist/core/orchestration/application/orchestration.service.js +42 -29
  17. package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
  18. package/dist/core/providers/application/provider.service.d.ts +34 -4
  19. package/dist/core/providers/application/provider.service.js +799 -82
  20. package/dist/core/providers/application/provider.service.js.map +1 -1
  21. package/dist/core/providers/index.d.ts +1 -1
  22. package/dist/core/providers/index.js.map +1 -1
  23. package/dist/core/providers/loader.js +4 -2
  24. package/dist/core/providers/loader.js.map +1 -1
  25. package/dist/core/providers/openclaw-gateway-rpc.d.ts +20 -0
  26. package/dist/core/providers/openclaw-gateway-rpc.js +629 -0
  27. package/dist/core/providers/openclaw-gateway-rpc.js.map +1 -0
  28. package/dist/core/providers/provider-module.d.ts +11 -0
  29. package/dist/core/providers/provider-module.js +8 -1
  30. package/dist/core/providers/provider-module.js.map +1 -1
  31. package/dist/core/providers/providers/claude-code/index.d.ts +4 -0
  32. package/dist/core/providers/providers/claude-code/index.js +32 -0
  33. package/dist/core/providers/providers/claude-code/index.js.map +1 -0
  34. package/dist/core/providers/providers/claude-code/provider.d.ts +13 -0
  35. package/dist/core/providers/providers/claude-code/provider.js +152 -0
  36. package/dist/core/providers/providers/claude-code/provider.js.map +1 -0
  37. package/dist/core/providers/providers/codex/index.d.ts +4 -0
  38. package/dist/core/providers/providers/codex/index.js +32 -0
  39. package/dist/core/providers/providers/codex/index.js.map +1 -0
  40. package/dist/core/providers/providers/codex/provider.d.ts +12 -0
  41. package/dist/core/providers/providers/codex/provider.js +156 -0
  42. package/dist/core/providers/providers/codex/provider.js.map +1 -0
  43. package/dist/core/providers/providers/cursor/index.d.ts +4 -0
  44. package/dist/core/providers/providers/cursor/index.js +32 -0
  45. package/dist/core/providers/providers/cursor/index.js.map +1 -0
  46. package/dist/core/providers/providers/cursor/provider.d.ts +12 -0
  47. package/dist/core/providers/providers/cursor/provider.js +161 -0
  48. package/dist/core/providers/providers/cursor/provider.js.map +1 -0
  49. package/dist/core/providers/providers/gemini-cli/index.d.ts +4 -0
  50. package/dist/core/providers/providers/gemini-cli/index.js +36 -0
  51. package/dist/core/providers/providers/gemini-cli/index.js.map +1 -0
  52. package/dist/core/providers/providers/gemini-cli/provider.d.ts +11 -0
  53. package/dist/core/providers/providers/gemini-cli/provider.js +122 -0
  54. package/dist/core/providers/providers/gemini-cli/provider.js.map +1 -0
  55. package/dist/core/providers/providers/openclaw/index.js +8 -0
  56. package/dist/core/providers/providers/openclaw/index.js.map +1 -1
  57. package/dist/core/providers/providers/openclaw/provider.js +1 -0
  58. package/dist/core/providers/providers/openclaw/provider.js.map +1 -1
  59. package/dist/core/providers/providers/opencode/index.d.ts +4 -0
  60. package/dist/core/providers/providers/opencode/index.js +27 -0
  61. package/dist/core/providers/providers/opencode/index.js.map +1 -0
  62. package/dist/core/providers/providers/opencode/provider.d.ts +12 -0
  63. package/dist/core/providers/providers/opencode/provider.js +130 -0
  64. package/dist/core/providers/providers/opencode/provider.js.map +1 -0
  65. package/dist/core/providers/providers/registry.d.ts +2 -0
  66. package/dist/core/providers/providers/registry.js +17 -0
  67. package/dist/core/providers/providers/registry.js.map +1 -0
  68. package/dist/core/providers/registry.d.ts +2 -1
  69. package/dist/core/providers/registry.js +40 -0
  70. package/dist/core/providers/registry.js.map +1 -1
  71. package/dist/core/providers/types.d.ts +1 -0
  72. package/dist/core/scenarios/application/scenario-runner.service.js +2 -1
  73. package/dist/core/scenarios/application/scenario-runner.service.js.map +1 -1
  74. package/dist/core/sessions/application/session.service.d.ts +0 -2
  75. package/dist/core/sessions/application/session.service.js +10 -73
  76. package/dist/core/sessions/application/session.service.js.map +1 -1
  77. package/dist/core/sessions/domain/session.d.ts +0 -3
  78. package/dist/core/sessions/domain/session.js.map +1 -1
  79. package/dist/core/skills/application/skill.service.js +4 -1
  80. package/dist/core/skills/application/skill.service.js.map +1 -1
  81. package/dist/core/templates/assets/ceo/BOOTSTRAP.md +3 -3
  82. package/dist/core/templates/assets/ceo/ROLE.md +3 -2
  83. package/dist/core/templates/assets/skills/og-boards/SKILL.md +65 -0
  84. package/dist/core/templates/default-templates.d.ts +1 -2
  85. package/dist/core/templates/default-templates.js +7 -7
  86. package/dist/core/templates/default-templates.js.map +1 -1
  87. 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 { InvalidProviderConfigError, UnsupportedProviderActionError } from "../index.js";
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
- const provider = registry.create(OPENCLAW_PROVIDER_ID);
22
- return [
23
- {
24
- id: provider.id,
25
- displayName: provider.displayName,
26
- kind: provider.kind,
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
- assertOpenClawProviderId(providerId);
36
+ const normalizedProviderId = normalizeProviderId(providerId);
33
37
  const registry = await this.getProviderRegistry();
34
- return registry.getProviderOnboarding(OPENCLAW_PROVIDER_ID);
38
+ return registry.getProviderOnboarding(normalizedProviderId);
35
39
  }
36
40
  async invokeProviderAuth(paths, providerId, options = {}) {
37
- assertOpenClawProviderId(providerId);
41
+ const normalizedProviderId = normalizeProviderId(providerId);
38
42
  const registry = await this.getProviderRegistry();
39
- const provider = registry.create(OPENCLAW_PROVIDER_ID);
40
- const env = await this.resolveProviderEnv(paths, options.env);
41
- this.logger.info("Invoking OpenClaw auth.");
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
- assertOpenClawProviderId(providerId);
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(providerId, configPath);
70
+ throw new InvalidProviderConfigError(normalizedProviderId, configPath);
65
71
  }
66
72
  if (!isProviderStoredConfig(parsed)) {
67
- throw new InvalidProviderConfigError(providerId, configPath, "schema mismatch");
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
- assertOpenClawProviderId(providerId);
73
- const providerDir = this.pathPort.join(paths.providersDir, OPENCLAW_PROVIDER_ID);
74
- const configPath = this.getProviderConfigPath(paths);
75
- const existing = await this.getProviderConfig(paths, OPENCLAW_PROVIDER_ID);
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: OPENCLAW_PROVIDER_ID,
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,116 +134,293 @@ 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(_paths, agentId) {
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: OPENCLAW_PROVIDER_ID
141
+ agentId: normalizedAgentId,
142
+ providerId,
132
143
  };
133
144
  }
134
- async setAgentProvider(_paths, agentId, providerId) {
135
- assertOpenClawProviderId(providerId);
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: OPENCLAW_PROVIDER_ID
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(OPENCLAW_PROVIDER_ID);
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: await this.resolveProviderEnv(paths, options.env),
150
- agent: provider.capabilities.agent ? options.agent || agentId : options.agent
184
+ env,
185
+ providerSessionId: mappedProviderSessionId,
186
+ agent: provider.capabilities.agent
187
+ ? options.agent || normalizedAgentId
188
+ : options.agent
151
189
  };
152
- this.logger.info("Invoking OpenClaw for agent.", {
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 = await provider.invoke(invokeOptions);
164
- result = await this.retryGatewayInvocationOnUvCwdFailure(paths, provider, invokeOptions, result);
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
- if (options.providerId) {
181
- assertOpenClawProviderId(options.providerId);
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(OPENCLAW_PROVIDER_ID);
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 result = await provider.createAgent({
189
- agentId,
190
- displayName: options.displayName,
191
- workspaceDir: options.workspaceDir,
192
- internalConfigDir: options.internalConfigDir,
193
- cwd: options.cwd,
194
- env: await this.resolveProviderEnv(paths, options.env),
195
- onStdout: options.onStdout,
196
- onStderr: options.onStderr
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
- if (options.providerId) {
206
- assertOpenClawProviderId(options.providerId);
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(OPENCLAW_PROVIDER_ID);
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 result = await provider.deleteAgent({
214
- agentId,
215
- cwd: options.cwd,
216
- env: await this.resolveProviderEnv(paths, options.env),
217
- onStdout: options.onStdout,
218
- onStderr: options.onStderr
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 getAgentRuntimeProfile(_paths, agentId) {
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(OPENCLAW_PROVIDER_ID);
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: "internal"
403
+ workspaceAccess: runtimePolicy.invocation.cwd,
404
+ roleSkillDirectories: [...runtimePolicy.skills.directories]
234
405
  };
235
406
  }
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
+ }
419
+ }
420
+ return [...directories].sort((left, right) => left.localeCompare(right));
421
+ }
236
422
  async restartLocalGateway(paths, inputEnv) {
237
- const env = await this.resolveProviderEnv(paths, inputEnv);
423
+ const env = await this.resolveProviderEnv(paths, OPENCLAW_PROVIDER_ID, inputEnv);
238
424
  const gatewayConfig = await this.getOpenClawGatewayConfig(paths, env);
239
425
  if (gatewayConfig.mode !== "local") {
240
426
  return false;
@@ -263,16 +449,325 @@ export class ProviderService {
263
449
  this.logger.warn("OpenClaw gateway restarted.");
264
450
  return true;
265
451
  }
266
- getProviderConfigPath(paths) {
267
- return this.pathPort.join(paths.providersDir, OPENCLAW_PROVIDER_ID, "config.json");
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
+ };
268
471
  }
269
- async resolveProviderEnv(paths, inputEnv) {
270
- const config = await this.getProviderConfig(paths, OPENCLAW_PROVIDER_ID);
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);
271
615
  return {
272
616
  ...(config?.env ?? {}),
273
617
  ...(inputEnv ?? process.env)
274
618
  };
275
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(),
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
+ }
276
771
  async retryGatewayInvocationOnUvCwdFailure(paths, provider, invokeOptions, result) {
277
772
  if (!isGatewayUvCwdFailure(result)) {
278
773
  return result;
@@ -347,6 +842,47 @@ function isGatewayUvCwdFailure(result) {
347
842
  const details = `${result.stderr}\n${result.stdout}`.toLowerCase();
348
843
  return details.includes("process.cwd failed") && details.includes("uv_cwd");
349
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
+ }
350
886
  function extractOpenClawGlobalArgs(raw) {
351
887
  const parts = (raw ?? "")
352
888
  .split(/\s+/)
@@ -369,9 +905,190 @@ function extractOpenClawGlobalArgs(raw) {
369
905
  }
370
906
  return result;
371
907
  }
372
- function assertOpenClawProviderId(providerId) {
373
- if (providerId.trim().toLowerCase() !== OPENCLAW_PROVIDER_ID) {
374
- throw new Error(`Only \"${OPENCLAW_PROVIDER_ID}\" is supported in this OpenGoat version.`);
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();
375
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;
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
+ };
376
1093
  }
377
1094
  //# sourceMappingURL=provider.service.js.map