agentopology 0.9.2 → 0.10.0

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 (168) hide show
  1. package/README.md +17 -19
  2. package/dist/__tests__/e2e/action-guard.test.d.ts +13 -0
  3. package/dist/__tests__/e2e/action-guard.test.d.ts.map +1 -0
  4. package/dist/__tests__/e2e/action-guard.test.js +299 -0
  5. package/dist/__tests__/e2e/action-guard.test.js.map +1 -0
  6. package/dist/__tests__/e2e/conformance.test.d.ts +13 -0
  7. package/dist/__tests__/e2e/conformance.test.d.ts.map +1 -0
  8. package/dist/__tests__/e2e/conformance.test.js +109 -0
  9. package/dist/__tests__/e2e/conformance.test.js.map +1 -0
  10. package/dist/__tests__/e2e/contract-drift.test.d.ts +11 -0
  11. package/dist/__tests__/e2e/contract-drift.test.d.ts.map +1 -0
  12. package/dist/__tests__/e2e/contract-drift.test.js +142 -0
  13. package/dist/__tests__/e2e/contract-drift.test.js.map +1 -0
  14. package/dist/__tests__/e2e/guard-check.test.d.ts +9 -0
  15. package/dist/__tests__/e2e/guard-check.test.d.ts.map +1 -0
  16. package/dist/__tests__/e2e/guard-check.test.js +316 -0
  17. package/dist/__tests__/e2e/guard-check.test.js.map +1 -0
  18. package/dist/__tests__/e2e/guard-target.test.d.ts +11 -0
  19. package/dist/__tests__/e2e/guard-target.test.d.ts.map +1 -0
  20. package/dist/__tests__/e2e/guard-target.test.js +198 -0
  21. package/dist/__tests__/e2e/guard-target.test.js.map +1 -0
  22. package/dist/__tests__/e2e/inventory.test.d.ts +18 -0
  23. package/dist/__tests__/e2e/inventory.test.d.ts.map +1 -0
  24. package/dist/__tests__/e2e/inventory.test.js +187 -0
  25. package/dist/__tests__/e2e/inventory.test.js.map +1 -0
  26. package/dist/__tests__/e2e/matcher.test.d.ts +14 -0
  27. package/dist/__tests__/e2e/matcher.test.d.ts.map +1 -0
  28. package/dist/__tests__/e2e/matcher.test.js +145 -0
  29. package/dist/__tests__/e2e/matcher.test.js.map +1 -0
  30. package/dist/__tests__/e2e/step-check.test.d.ts +9 -0
  31. package/dist/__tests__/e2e/step-check.test.d.ts.map +1 -0
  32. package/dist/__tests__/e2e/step-check.test.js +158 -0
  33. package/dist/__tests__/e2e/step-check.test.js.map +1 -0
  34. package/dist/__tests__/e2e/strength.test.d.ts +17 -0
  35. package/dist/__tests__/e2e/strength.test.d.ts.map +1 -0
  36. package/dist/__tests__/e2e/strength.test.js +130 -0
  37. package/dist/__tests__/e2e/strength.test.js.map +1 -0
  38. package/dist/__tests__/e2e/verified-on.test.d.ts +2 -0
  39. package/dist/__tests__/e2e/verified-on.test.d.ts.map +1 -0
  40. package/dist/__tests__/e2e/verified-on.test.js +57 -0
  41. package/dist/__tests__/e2e/verified-on.test.js.map +1 -0
  42. package/dist/__tests__/open-findings.test.js +1 -0
  43. package/dist/__tests__/open-findings.test.js.map +1 -1
  44. package/dist/bindings/__tests__/bindings.test.js +6 -686
  45. package/dist/bindings/__tests__/bindings.test.js.map +1 -1
  46. package/dist/bindings/__tests__/defaults-agreement.test.js +0 -12
  47. package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -1
  48. package/dist/bindings/__tests__/hook-command.test.js +2 -2
  49. package/dist/bindings/__tests__/hook-command.test.js.map +1 -1
  50. package/dist/bindings/__tests__/mod-plugin.test.js +2 -2
  51. package/dist/bindings/__tests__/mod-plugin.test.js.map +1 -1
  52. package/dist/bindings/claude-code.d.ts +45 -1
  53. package/dist/bindings/claude-code.d.ts.map +1 -1
  54. package/dist/bindings/claude-code.js +181 -15
  55. package/dist/bindings/claude-code.js.map +1 -1
  56. package/dist/bindings/index.d.ts +5 -5
  57. package/dist/bindings/index.d.ts.map +1 -1
  58. package/dist/bindings/index.js +9 -16
  59. package/dist/bindings/index.js.map +1 -1
  60. package/dist/bindings/types.d.ts +6 -1
  61. package/dist/bindings/types.d.ts.map +1 -1
  62. package/dist/cli/__tests__/cli.test.js +25 -0
  63. package/dist/cli/__tests__/cli.test.js.map +1 -1
  64. package/dist/cli/index.js +222 -8
  65. package/dist/cli/index.js.map +1 -1
  66. package/dist/conformance/claude-code.d.ts +99 -0
  67. package/dist/conformance/claude-code.d.ts.map +1 -0
  68. package/dist/conformance/claude-code.js +242 -0
  69. package/dist/conformance/claude-code.js.map +1 -0
  70. package/dist/conformance/extract.d.ts +70 -0
  71. package/dist/conformance/extract.d.ts.map +1 -0
  72. package/dist/conformance/extract.js +185 -0
  73. package/dist/conformance/extract.js.map +1 -0
  74. package/dist/docs/content.d.ts.map +1 -1
  75. package/dist/docs/content.js +57 -32
  76. package/dist/docs/content.js.map +1 -1
  77. package/dist/guard/check.d.ts +53 -0
  78. package/dist/guard/check.d.ts.map +1 -0
  79. package/dist/guard/check.js +366 -0
  80. package/dist/guard/check.js.map +1 -0
  81. package/dist/guard/target.d.ts +45 -0
  82. package/dist/guard/target.d.ts.map +1 -0
  83. package/dist/guard/target.js +168 -0
  84. package/dist/guard/target.js.map +1 -0
  85. package/dist/import/__tests__/import.test.js +0 -554
  86. package/dist/import/__tests__/import.test.js.map +1 -1
  87. package/dist/import/index.d.ts +0 -1
  88. package/dist/import/index.d.ts.map +1 -1
  89. package/dist/import/index.js +1 -7
  90. package/dist/import/index.js.map +1 -1
  91. package/dist/import/serializer.d.ts.map +1 -1
  92. package/dist/import/serializer.js +41 -0
  93. package/dist/import/serializer.js.map +1 -1
  94. package/dist/index.d.ts +0 -4
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +0 -4
  97. package/dist/index.js.map +1 -1
  98. package/dist/inventory/index.d.ts +65 -0
  99. package/dist/inventory/index.d.ts.map +1 -0
  100. package/dist/inventory/index.js +272 -0
  101. package/dist/inventory/index.js.map +1 -0
  102. package/dist/parser/ast.d.ts +46 -0
  103. package/dist/parser/ast.d.ts.map +1 -1
  104. package/dist/parser/ast.js.map +1 -1
  105. package/dist/parser/index.d.ts +6 -1
  106. package/dist/parser/index.d.ts.map +1 -1
  107. package/dist/parser/index.js +66 -4
  108. package/dist/parser/index.js.map +1 -1
  109. package/dist/parser/known-fields.d.ts +3 -0
  110. package/dist/parser/known-fields.d.ts.map +1 -1
  111. package/dist/parser/known-fields.js +5 -3
  112. package/dist/parser/known-fields.js.map +1 -1
  113. package/dist/parser/ledger.d.ts +36 -0
  114. package/dist/parser/ledger.d.ts.map +1 -0
  115. package/dist/parser/ledger.js +72 -0
  116. package/dist/parser/ledger.js.map +1 -0
  117. package/dist/parser/run.d.ts +6 -0
  118. package/dist/parser/run.d.ts.map +1 -1
  119. package/dist/parser/run.js +10 -0
  120. package/dist/parser/run.js.map +1 -1
  121. package/dist/parser/validator.d.ts.map +1 -1
  122. package/dist/parser/validator.js +255 -1
  123. package/dist/parser/validator.js.map +1 -1
  124. package/dist/scaffold/manifest.d.ts.map +1 -1
  125. package/dist/scaffold/manifest.js +0 -8
  126. package/dist/scaffold/manifest.js.map +1 -1
  127. package/dist/scaffold/merge.d.ts.map +1 -1
  128. package/dist/scaffold/merge.js +1 -3
  129. package/dist/scaffold/merge.js.map +1 -1
  130. package/dist/step/check.d.ts +56 -0
  131. package/dist/step/check.d.ts.map +1 -0
  132. package/dist/step/check.js +125 -0
  133. package/dist/step/check.js.map +1 -0
  134. package/dist/sync/index.d.ts +0 -2
  135. package/dist/sync/index.d.ts.map +1 -1
  136. package/dist/sync/index.js +0 -109
  137. package/dist/sync/index.js.map +1 -1
  138. package/package.json +3 -1
  139. package/spec/claude-code/README.md +19 -0
  140. package/spec/claude-code/hooks-contract.2.1.295.json +143 -0
  141. package/spec/claude-code/hooks-contract.json +142 -0
  142. package/spec/grammar.md +45 -7
  143. package/spec/testing.md +15 -0
  144. package/spec/validation.md +11 -2
  145. package/dist/__tests__/live/zz-debug.test.d.ts +0 -2
  146. package/dist/__tests__/live/zz-debug.test.d.ts.map +0 -1
  147. package/dist/__tests__/live/zz-debug.test.js +0 -4
  148. package/dist/__tests__/live/zz-debug.test.js.map +0 -1
  149. package/dist/bindings/copilot-cli.d.ts +0 -20
  150. package/dist/bindings/copilot-cli.d.ts.map +0 -1
  151. package/dist/bindings/copilot-cli.js +0 -1516
  152. package/dist/bindings/copilot-cli.js.map +0 -1
  153. package/dist/bindings/gemini-cli.d.ts +0 -18
  154. package/dist/bindings/gemini-cli.d.ts.map +0 -1
  155. package/dist/bindings/gemini-cli.js +0 -1318
  156. package/dist/bindings/gemini-cli.js.map +0 -1
  157. package/dist/bindings/kiro.d.ts +0 -17
  158. package/dist/bindings/kiro.d.ts.map +0 -1
  159. package/dist/bindings/kiro.js +0 -1615
  160. package/dist/bindings/kiro.js.map +0 -1
  161. package/dist/bindings/openclaw.d.ts +0 -18
  162. package/dist/bindings/openclaw.d.ts.map +0 -1
  163. package/dist/bindings/openclaw.js +0 -1947
  164. package/dist/bindings/openclaw.js.map +0 -1
  165. package/dist/import/openclaw.d.ts +0 -70
  166. package/dist/import/openclaw.d.ts.map +0 -1
  167. package/dist/import/openclaw.js +0 -1060
  168. package/dist/import/openclaw.js.map +0 -1
@@ -1,1060 +0,0 @@
1
- /**
2
- * OpenClaw importer — reads an OpenClaw workspace directory structure and
3
- * constructs a TopologyAST suitable for serialization to .at format.
4
- *
5
- * Reverses the OpenClaw binding ({@link ../bindings/openclaw.ts}) by parsing:
6
- * - openclaw.json — agent list, models, providers, env, settings
7
- * - SOUL.md — topology metadata, roles, params, triggers, interface
8
- * - AGENTS.md — agents, groups, humans, flow, gates, schedule, hooks
9
- * - MEMORY.md — workspace, domains, stores
10
- * - skills/ — skill definitions (YAML frontmatter)
11
- * - cron/jobs.json — scheduled jobs (fallback)
12
- *
13
- * @module
14
- */
15
- // ---------------------------------------------------------------------------
16
- // Helpers
17
- // ---------------------------------------------------------------------------
18
- /** Reverse Title Case to kebab-case: "Linkedin Soldier" → "linkedin-soldier" */
19
- function toId(title) {
20
- return title.trim().toLowerCase().replace(/\s+/g, "-");
21
- }
22
- /**
23
- * Reverse model mapping: strip provider prefix and reverse short aliases.
24
- *
25
- * The OpenClaw binding prepends the provider name (e.g., "openrouter/") to
26
- * model strings. This function strips it back and converts Anthropic model
27
- * names to their short aliases (opus, sonnet, haiku).
28
- */
29
- function reverseMapModel(openclawModel, providerNames) {
30
- let model = openclawModel;
31
- // Strip known provider prefixes
32
- for (const provider of providerNames) {
33
- if (model.startsWith(`${provider}/`)) {
34
- model = model.slice(provider.length + 1);
35
- break;
36
- }
37
- }
38
- // Reverse Anthropic short aliases
39
- const REVERSE_ALIASES = {
40
- "claude-opus-4-6": "opus",
41
- "claude-sonnet-4-6": "sonnet",
42
- "claude-haiku-4-5": "haiku",
43
- };
44
- // Check if the last segment matches an alias
45
- const lastSlash = model.lastIndexOf("/");
46
- const baseName = lastSlash >= 0 ? model.slice(lastSlash + 1) : model;
47
- if (REVERSE_ALIASES[baseName]) {
48
- return REVERSE_ALIASES[baseName];
49
- }
50
- return model;
51
- }
52
- /** Reverse permission mapping from OpenClaw → .at format. */
53
- function reversePermission(perm) {
54
- switch (perm) {
55
- case "autonomous":
56
- return "auto";
57
- case "supervised":
58
- return "plan";
59
- case "interactive":
60
- return "confirm";
61
- case "unrestricted":
62
- return "bypassPermissions";
63
- default:
64
- return perm;
65
- }
66
- }
67
- function parseFrontmatter(content) {
68
- const fields = {};
69
- let body = content;
70
- if (!content.startsWith("---")) {
71
- return { fields, body };
72
- }
73
- const endIdx = content.indexOf("---", 3);
74
- if (endIdx === -1)
75
- return { fields, body };
76
- const yaml = content.slice(3, endIdx).trim();
77
- body = content.slice(endIdx + 3).trim();
78
- for (const line of yaml.split("\n")) {
79
- const trimmed = line.trim();
80
- if (!trimmed)
81
- continue;
82
- const colonIdx = trimmed.indexOf(":");
83
- if (colonIdx === -1)
84
- continue;
85
- const key = trimmed.slice(0, colonIdx).trim();
86
- const rawValue = trimmed.slice(colonIdx + 1).trim();
87
- if (rawValue === "true") {
88
- fields[key] = true;
89
- continue;
90
- }
91
- if (rawValue === "false") {
92
- fields[key] = false;
93
- continue;
94
- }
95
- fields[key] = rawValue.replace(/^["']|["']$/g, "");
96
- }
97
- return { fields, body };
98
- }
99
- /**
100
- * Extract a section from markdown by heading, returning text until the next
101
- * heading of equal or higher level.
102
- */
103
- function extractSection(content, heading, stopLevel) {
104
- const headingLevel = heading.match(/^#+/)?.[0].length ?? 2;
105
- const level = stopLevel ?? headingLevel;
106
- const pattern = new RegExp(`^${heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
107
- const match = pattern.exec(content);
108
- if (!match)
109
- return null;
110
- const startIdx = match.index + match[0].length;
111
- const rest = content.slice(startIdx);
112
- // Find next heading of same or higher level
113
- const stopPattern = new RegExp(`^#{1,${level}}\\s`, "m");
114
- const stopMatch = stopPattern.exec(rest);
115
- const section = stopMatch ? rest.slice(0, stopMatch.index) : rest;
116
- return section.trim() || null;
117
- }
118
- /**
119
- * Extract all level-3 subsections from a section body.
120
- * Returns array of [title, body] tuples.
121
- */
122
- function extractSubsections(sectionBody, level = 3) {
123
- const prefix = "#".repeat(level);
124
- const pattern = new RegExp(`^${prefix} (.+)$`, "gm");
125
- const results = [];
126
- let match;
127
- const matches = [];
128
- while ((match = pattern.exec(sectionBody)) !== null) {
129
- matches.push({ title: match[1].trim(), start: match.index + match[0].length });
130
- }
131
- for (let i = 0; i < matches.length; i++) {
132
- const end = i + 1 < matches.length
133
- ? sectionBody.lastIndexOf(`\n${prefix} `, matches[i + 1].start - matches[i + 1].title.length - prefix.length - 2) + 1
134
- : sectionBody.length;
135
- // Simpler: just slice from current start to next match start (minus the heading line)
136
- const nextStart = i + 1 < matches.length
137
- ? sectionBody.indexOf(`\n${prefix} ${matches[i + 1].title}`, matches[i].start)
138
- : sectionBody.length;
139
- const body = sectionBody.slice(matches[i].start, nextStart).trim();
140
- results.push([matches[i].title, body]);
141
- }
142
- return results;
143
- }
144
- /** Parse a `- **Key:** value` line from markdown. */
145
- function extractField(text, key) {
146
- const pattern = new RegExp(`-\\s*\\*\\*${key}:\\*\\*\\s*(.+)`, "i");
147
- const match = pattern.exec(text);
148
- return match ? match[1].trim() : undefined;
149
- }
150
- export function parseOpenClawJson(content) {
151
- const json = JSON.parse(content);
152
- const result = {
153
- agents: [],
154
- providers: [],
155
- providerNames: [],
156
- settings: {},
157
- env: {},
158
- extensions: {},
159
- };
160
- // Default model
161
- if (json.agents?.defaults?.model?.primary) {
162
- result.defaultModel = json.agents.defaults.model.primary;
163
- }
164
- // Agent list
165
- if (json.agents?.list) {
166
- for (const agent of json.agents.list) {
167
- const entry = { id: agent.id };
168
- if (agent.model?.primary)
169
- entry.model = agent.model.primary;
170
- if (agent.model?.fallbacks)
171
- entry.fallbacks = agent.model.fallbacks;
172
- if (agent.subagents?.allowAgents)
173
- entry.subagents = agent.subagents.allowAgents;
174
- result.agents.push(entry);
175
- }
176
- }
177
- // Providers
178
- if (json.models?.providers) {
179
- for (const [name, config] of Object.entries(json.models.providers)) {
180
- result.providerNames.push(name);
181
- const provider = {
182
- name,
183
- models: (config.models ?? []).map((m) => typeof m === "string" ? m : m.id ?? m.name),
184
- extra: {},
185
- };
186
- if (config.apiKey)
187
- provider.apiKey = config.apiKey;
188
- if (config.baseUrl)
189
- provider.baseUrl = config.baseUrl;
190
- result.providers.push(provider);
191
- }
192
- }
193
- // Tools settings
194
- if (json.tools) {
195
- if (json.tools.allow?.length)
196
- result.settings.allow = json.tools.allow;
197
- if (json.tools.deny?.length)
198
- result.settings.deny = json.tools.deny;
199
- }
200
- // Env
201
- if (json.env) {
202
- result.env = { ...json.env };
203
- }
204
- // Gateway → extensions
205
- if (json.gateway) {
206
- result.extensions["gateway-port"] = json.gateway.port;
207
- if (json.gateway.auth) {
208
- result.extensions["auth-mode"] = json.gateway.auth.mode;
209
- }
210
- }
211
- return result;
212
- }
213
- export function parseSoulMd(content) {
214
- const result = {
215
- roles: {},
216
- params: [],
217
- interfaceEndpoints: null,
218
- triggers: [],
219
- env: {},
220
- };
221
- // Quote block at top → description
222
- const quoteMatch = content.match(/^>\s*(.+)/m);
223
- if (quoteMatch) {
224
- result.description = quoteMatch[1].trim();
225
- }
226
- // ## Identity
227
- const identity = extractSection(content, "## Identity");
228
- if (identity) {
229
- const nameMatch = identity.match(/Name:\s*(.+)/);
230
- if (nameMatch)
231
- result.name = toId(nameMatch[1].trim());
232
- const versionMatch = identity.match(/Version:\s*(.+)/);
233
- if (versionMatch)
234
- result.version = versionMatch[1].trim();
235
- const patternsMatch = identity.match(/Patterns:\s*(.+)/);
236
- if (patternsMatch) {
237
- result.patterns = patternsMatch[1].split(/,\s*/).map((p) => p.trim());
238
- }
239
- }
240
- // ## Mission (fallback description)
241
- if (!result.description) {
242
- const mission = extractSection(content, "## Mission");
243
- if (mission)
244
- result.description = mission.split("\n")[0].trim();
245
- }
246
- // ## Roles
247
- const rolesSection = extractSection(content, "## Roles");
248
- if (rolesSection) {
249
- const roleSubs = extractSubsections(rolesSection);
250
- for (const [title, body] of roleSubs) {
251
- const roleId = toId(title);
252
- // Body is the role description (first non-empty line)
253
- const desc = body.split("\n").find((l) => l.trim())?.trim();
254
- if (desc)
255
- result.roles[roleId] = desc;
256
- }
257
- }
258
- // ## Ethical Guardrails — extract meta info
259
- const guardrails = extractSection(content, "## Ethical Guardrails");
260
- if (guardrails) {
261
- const permMatch = guardrails.match(/Permission model:\s*(.+)/);
262
- // stored but not directly mapped to AST — informational
263
- const domainMatch = guardrails.match(/Domain:\s*(.+)/);
264
- if (domainMatch)
265
- result.domain = domainMatch[1].trim();
266
- }
267
- // ## Parameters
268
- const paramsSection = extractSection(content, "## Parameters");
269
- if (paramsSection) {
270
- // Format: - **name**: type (required) = default
271
- // Or: - **name**: type = default
272
- const paramPattern = /- \*\*(\S+)\*\*:\s*(\w+)(?:\s*\(required\))?(?:\s*=\s*(.+))?/g;
273
- let paramMatch;
274
- while ((paramMatch = paramPattern.exec(paramsSection)) !== null) {
275
- const name = paramMatch[1];
276
- const type = paramMatch[2];
277
- const defaultVal = paramMatch[3]?.trim();
278
- const required = !defaultVal && paramsSection.includes(`${name}**:`) && paramsSection.includes("(required)");
279
- result.params.push({
280
- name,
281
- type,
282
- default: defaultVal,
283
- required: !defaultVal,
284
- });
285
- }
286
- }
287
- // ## Interface
288
- const interfaceSection = extractSection(content, "## Interface");
289
- if (interfaceSection) {
290
- const entryMatch = interfaceSection.match(/Entry:\s*(\S+)/);
291
- const exitMatch = interfaceSection.match(/Exit:\s*(\S+)/);
292
- if (entryMatch && exitMatch) {
293
- result.interfaceEndpoints = {
294
- entry: entryMatch[1],
295
- exit: exitMatch[1],
296
- };
297
- }
298
- }
299
- // ## Triggers
300
- const triggersSection = extractSection(content, "## Triggers");
301
- if (triggersSection) {
302
- const triggerSubs = extractSubsections(triggersSection);
303
- for (const [title, body] of triggerSubs) {
304
- const trigger = { name: toId(title), pattern: "" };
305
- const patternMatch = body.match(/Pattern:\s*`?([^`\n]+)`?/);
306
- if (patternMatch)
307
- trigger.pattern = patternMatch[1].trim();
308
- const argMatch = body.match(/Argument:\s*(\S+)/);
309
- if (argMatch)
310
- trigger.argument = argMatch[1];
311
- result.triggers.push(trigger);
312
- }
313
- }
314
- // ## Environment
315
- const envSection = extractSection(content, "## Environment");
316
- if (envSection) {
317
- const envPattern = /- `(\S+)`:\s*(.+)/g;
318
- let envMatch;
319
- while ((envMatch = envPattern.exec(envSection)) !== null) {
320
- result.env[envMatch[1]] = envMatch[2].trim();
321
- }
322
- }
323
- return result;
324
- }
325
- export function parseAgentsMd(content) {
326
- const result = {
327
- agents: [],
328
- groups: [],
329
- humans: [],
330
- gates: [],
331
- edges: [],
332
- schedules: [],
333
- hooks: [],
334
- metering: null,
335
- };
336
- // ## Agents
337
- const agentsSection = extractSection(content, "## Agents");
338
- if (agentsSection) {
339
- const agentSubs = extractSubsections(agentsSection);
340
- for (const [title, body] of agentSubs) {
341
- const agent = parseAgentSubsection(toId(title), body);
342
- result.agents.push(agent);
343
- }
344
- }
345
- // ## Human Pause Points
346
- const humanSection = extractSection(content, "## Human Pause Points");
347
- if (humanSection) {
348
- const humanSubs = extractSubsections(humanSection);
349
- for (const [title, body] of humanSubs) {
350
- const human = {
351
- type: "human",
352
- id: toId(title),
353
- label: toId(title),
354
- };
355
- const action = extractField(body, "Action");
356
- if (action)
357
- human.description = action;
358
- const timeout = extractField(body, "Timeout");
359
- if (timeout)
360
- human.timeout = timeout;
361
- const onTimeout = extractField(body, "On timeout");
362
- if (onTimeout)
363
- human.onTimeout = onTimeout;
364
- result.humans.push(human);
365
- }
366
- }
367
- // ## Group Chat Coordination
368
- const groupSection = extractSection(content, "## Group Chat Coordination");
369
- if (groupSection) {
370
- const groupSubs = extractSubsections(groupSection);
371
- for (const [title, body] of groupSubs) {
372
- const group = {
373
- type: "group",
374
- id: toId(title),
375
- label: toId(title),
376
- members: [],
377
- };
378
- const members = extractField(body, "Members");
379
- if (members)
380
- group.members = members.split(/,\s*/);
381
- const speaker = extractField(body, "Speaker selection");
382
- if (speaker)
383
- group.speakerSelection = speaker;
384
- const maxRounds = extractField(body, "Max rounds");
385
- if (maxRounds)
386
- group.maxRounds = parseInt(maxRounds, 10);
387
- const termination = extractField(body, "Termination");
388
- if (termination)
389
- group.termination = termination;
390
- const description = extractField(body, "Description");
391
- if (description)
392
- group.description = description;
393
- const timeout = extractField(body, "Timeout");
394
- if (timeout)
395
- group.timeout = timeout;
396
- result.groups.push(group);
397
- }
398
- }
399
- // ## Flow → ### Execution Order
400
- const flowSection = extractSection(content, "## Flow");
401
- if (flowSection) {
402
- const execOrder = extractSection(flowSection, "### Execution Order", 3);
403
- if (execOrder) {
404
- result.edges = parseEdgeLines(execOrder);
405
- }
406
- }
407
- // ## Gates
408
- const gatesSection = extractSection(content, "## Gates");
409
- if (gatesSection) {
410
- const gateSubs = extractSubsections(gatesSection);
411
- for (const [title, body] of gateSubs) {
412
- const gate = {
413
- type: "gate",
414
- id: toId(title),
415
- label: toId(title),
416
- };
417
- const after = extractField(body, "After");
418
- if (after)
419
- gate.after = after;
420
- const before = extractField(body, "Before");
421
- if (before)
422
- gate.before = before;
423
- const script = extractField(body, "Script");
424
- if (script)
425
- gate.run = script;
426
- const onFail = extractField(body, "On failure");
427
- if (onFail)
428
- gate.onFail = onFail;
429
- const behavior = extractField(body, "Behavior");
430
- if (behavior)
431
- gate.behavior = behavior;
432
- const checks = extractField(body, "Checks");
433
- if (checks)
434
- gate.checks = checks.split(/,\s*/);
435
- const retry = extractField(body, "Retry");
436
- if (retry)
437
- gate.retry = parseInt(retry, 10);
438
- const timeout = extractField(body, "Timeout");
439
- if (timeout)
440
- gate.timeout = timeout;
441
- result.gates.push(gate);
442
- }
443
- }
444
- // ## Schedule
445
- const scheduleSection = extractSection(content, "## Schedule");
446
- if (scheduleSection) {
447
- const scheduleSubs = extractSubsections(scheduleSection);
448
- for (const [title, body] of scheduleSubs) {
449
- const job = {
450
- id: toId(title),
451
- enabled: true,
452
- };
453
- const agent = extractField(body, "Agent");
454
- if (agent)
455
- job.agent = agent;
456
- const cron = extractField(body, "Cron");
457
- if (cron)
458
- job.cron = cron.replace(/`/g, "").trim();
459
- const enabled = extractField(body, "Enabled");
460
- if (enabled)
461
- job.enabled = enabled === "true";
462
- result.schedules.push(job);
463
- }
464
- }
465
- // ## Hooks
466
- const hooksSection = extractSection(content, "## Hooks");
467
- if (hooksSection) {
468
- const hookSubs = extractSubsections(hooksSection);
469
- for (const [title, body] of hookSubs) {
470
- const hook = {
471
- name: toId(title),
472
- on: "",
473
- matcher: "",
474
- run: "",
475
- };
476
- const event = extractField(body, "Event");
477
- if (event)
478
- hook.on = event;
479
- const matcher = extractField(body, "Matcher");
480
- if (matcher)
481
- hook.matcher = matcher;
482
- const run = extractField(body, "Run");
483
- if (run)
484
- hook.run = run;
485
- const type = extractField(body, "Type");
486
- if (type)
487
- hook.type = type;
488
- const timeout = extractField(body, "Timeout");
489
- if (timeout)
490
- hook.timeout = parseInt(timeout, 10);
491
- result.hooks.push(hook);
492
- }
493
- }
494
- // ### Metering (within the topology overview, after ---)
495
- const meteringSection = extractSection(content, "### Metering", 3);
496
- if (meteringSection) {
497
- const track = extractField(meteringSection, "Track") ?? meteringSection.match(/Track:\s*(.+)/)?.[1];
498
- const per = extractField(meteringSection, "Per") ?? meteringSection.match(/Per:\s*(.+)/)?.[1];
499
- const outputMatch = meteringSection.match(/Output:\s*(\S+)\s*\((\w+)\)/);
500
- const pricing = extractField(meteringSection, "Pricing") ?? meteringSection.match(/Pricing:\s*(.+)/)?.[1];
501
- if (track && per && outputMatch) {
502
- result.metering = {
503
- track: track.split(/,\s*/),
504
- per: per.split(/,\s*/),
505
- output: outputMatch[1],
506
- format: outputMatch[2],
507
- pricing: pricing ?? "",
508
- };
509
- }
510
- }
511
- return result;
512
- }
513
- // ---------------------------------------------------------------------------
514
- // Agent subsection parser (within AGENTS.md ## Agents)
515
- // ---------------------------------------------------------------------------
516
- function parseAgentSubsection(agentId, body) {
517
- const agent = {
518
- type: "agent",
519
- id: agentId,
520
- label: agentId,
521
- };
522
- // Extract fields before #### Instructions
523
- const instructionsIdx = body.indexOf("#### Instructions");
524
- const propsText = instructionsIdx >= 0 ? body.slice(0, instructionsIdx) : body;
525
- const promptText = instructionsIdx >= 0 ? body.slice(instructionsIdx + "#### Instructions".length) : null;
526
- // Model — strip the "anthropic/" prefix the binding sometimes adds
527
- const model = extractField(propsText, "Model");
528
- if (model)
529
- agent.model = model;
530
- // Phase
531
- const phase = extractField(propsText, "Phase");
532
- if (phase)
533
- agent.phase = parseFloat(phase);
534
- // Role
535
- const role = extractField(propsText, "Role");
536
- if (role)
537
- agent.role = role;
538
- // Tools
539
- const tools = extractField(propsText, "Tools allowed");
540
- if (tools && tools !== "none")
541
- agent.tools = tools.split(/,\s*/);
542
- const denied = extractField(propsText, "Tools denied");
543
- if (denied && denied !== "none")
544
- agent.disallowedTools = denied.split(/,\s*/);
545
- // Memory access
546
- const reads = extractField(propsText, "Reads");
547
- if (reads)
548
- agent.reads = reads.split(/,\s*/);
549
- const writes = extractField(propsText, "Writes");
550
- if (writes)
551
- agent.writes = writes.split(/,\s*/);
552
- // Skills
553
- const skills = extractField(propsText, "Skills");
554
- if (skills)
555
- agent.skills = skills.split(/,\s*/);
556
- // Max turns
557
- const maxTurns = extractField(propsText, "Max turns");
558
- if (maxTurns)
559
- agent.maxTurns = parseInt(maxTurns, 10);
560
- // Permissions
561
- const permissions = extractField(propsText, "Permissions");
562
- if (permissions)
563
- agent.permissions = reversePermission(permissions);
564
- // Outputs
565
- const outputsMatch = propsText.match(/- \*\*Outputs:\*\*\n((?:\s+- .+\n?)+)/);
566
- if (outputsMatch) {
567
- const outputs = {};
568
- for (const line of outputsMatch[1].split("\n")) {
569
- const oMatch = line.trim().match(/^-\s+(\S+):\s*(.+)/);
570
- if (oMatch) {
571
- outputs[oMatch[1]] = oMatch[2].split(/\s*\|\s*/);
572
- }
573
- }
574
- if (Object.keys(outputs).length > 0)
575
- agent.outputs = outputs;
576
- }
577
- // Scale
578
- const scale = extractField(propsText, "Scale");
579
- if (scale) {
580
- const scaleMatch = scale.match(/(\d+)-(\d+)\s+instances\s+by\s+(.+)/);
581
- if (scaleMatch) {
582
- agent.scale = {
583
- mode: "auto",
584
- by: scaleMatch[3].trim(),
585
- min: parseInt(scaleMatch[1], 10),
586
- max: parseInt(scaleMatch[2], 10),
587
- batchSize: null,
588
- };
589
- }
590
- }
591
- // Prompt
592
- if (promptText) {
593
- // The prompt text runs until we hit trailing property lines (- **Key:** ...)
594
- // Split: prompt content is everything that's NOT a trailing `- **` line
595
- const lines = promptText.split("\n");
596
- const promptLines = [];
597
- const trailingProps = [];
598
- let hitTrailingProps = false;
599
- // Walk from the end to find trailing property lines
600
- for (let i = lines.length - 1; i >= 0; i--) {
601
- const line = lines[i].trim();
602
- if (line.match(/^- \*\*/) || (hitTrailingProps && line === "")) {
603
- trailingProps.unshift(lines[i]);
604
- hitTrailingProps = true;
605
- }
606
- else {
607
- break;
608
- }
609
- }
610
- const promptEndIdx = lines.length - trailingProps.length;
611
- for (let i = 0; i < promptEndIdx; i++) {
612
- promptLines.push(lines[i]);
613
- }
614
- const prompt = promptLines.join("\n").trim();
615
- if (prompt)
616
- agent.prompt = prompt;
617
- // Parse trailing properties
618
- const trailingText = trailingProps.join("\n");
619
- parseTrailingAgentProps(agent, trailingText);
620
- }
621
- // Also parse trailing props that might be in the propsText (after description/before instructions)
622
- parseTrailingAgentProps(agent, propsText);
623
- return agent;
624
- }
625
- /** Parse `- **Key:** value` properties that appear after agent instructions. */
626
- function parseTrailingAgentProps(agent, text) {
627
- // Timeout
628
- const timeout = extractField(text, "Maximum execution time");
629
- if (timeout && !agent.timeout)
630
- agent.timeout = timeout;
631
- // On failure
632
- const onFail = extractField(text, "On failure");
633
- if (onFail && !agent.onFail)
634
- agent.onFail = onFail;
635
- // Retry — format: "max N attempts" or structured
636
- const retry = extractField(text, "Retry");
637
- if (retry && agent.retry == null) {
638
- agent.retry = parseRetryString(retry);
639
- }
640
- // Thinking
641
- const thinking = extractField(text, "Reasoning level");
642
- if (thinking && !agent.thinking)
643
- agent.thinking = thinking;
644
- // Output format
645
- const outputFormat = extractField(text, "Output format");
646
- if (outputFormat && !agent.outputFormat)
647
- agent.outputFormat = outputFormat;
648
- // Log level
649
- const logLevel = extractField(text, "Log verbosity");
650
- if (logLevel && !agent.logLevel)
651
- agent.logLevel = logLevel;
652
- // Join
653
- const join = extractField(text, "Wait for");
654
- if (join && !agent.join)
655
- agent.join = join;
656
- // Circuit breaker
657
- const cb = extractField(text, "Circuit breaker");
658
- if (cb && !agent.circuitBreaker) {
659
- agent.circuitBreaker = parseCircuitBreakerString(cb);
660
- }
661
- // Compensates
662
- const compensates = extractField(text, "Compensates");
663
- if (compensates && !agent.compensates) {
664
- agent.compensates = compensates.replace(/\s*\(saga rollback\)/, "").trim();
665
- }
666
- // Rate limit
667
- const rateLimit = extractField(text, "Rate limit");
668
- if (rateLimit && !agent.rateLimit)
669
- agent.rateLimit = rateLimit;
670
- // Produces / Consumes
671
- const produces = extractField(text, "Produces");
672
- if (produces && !agent.produces)
673
- agent.produces = produces.split(/,\s*/);
674
- const consumes = extractField(text, "Consumes");
675
- if (consumes && !agent.consumes)
676
- agent.consumes = consumes.split(/,\s*/);
677
- }
678
- /** Parse retry string: "max N attempts" or structured format. */
679
- function parseRetryString(s) {
680
- const simpleMatch = s.match(/max (\d+) attempts?/);
681
- if (simpleMatch) {
682
- // Check for additional structured fields
683
- const parts = s.split(/,\s*/);
684
- if (parts.length === 1) {
685
- return parseInt(simpleMatch[1], 10);
686
- }
687
- // Structured
688
- const config = { max: parseInt(simpleMatch[1], 10) };
689
- for (const part of parts.slice(1)) {
690
- const kv = part.match(/(\S+):\s*(.+)/);
691
- if (!kv)
692
- continue;
693
- const key = kv[1].trim();
694
- const val = kv[2].trim();
695
- if (key === "backoff")
696
- config.backoff = val;
697
- else if (key === "interval")
698
- config.interval = val;
699
- else if (key === "max interval")
700
- config.maxInterval = val;
701
- else if (key === "jitter" && val === "on")
702
- config.jitter = true;
703
- else if (key === "non-retryable")
704
- config.nonRetryable = val.split(/,\s*/);
705
- }
706
- if (config.backoff || config.interval || config.jitter || config.nonRetryable) {
707
- return config;
708
- }
709
- return config.max;
710
- }
711
- // Simple integer
712
- const num = parseInt(s, 10);
713
- return isNaN(num) ? 1 : num;
714
- }
715
- /** Parse circuit breaker string: "threshold=N, window=X, cooldown=Y" */
716
- function parseCircuitBreakerString(s) {
717
- const threshold = s.match(/threshold=(\d+)/);
718
- const window = s.match(/window=(\S+)/);
719
- const cooldown = s.match(/cooldown=(\S+)/);
720
- if (threshold && window && cooldown) {
721
- return {
722
- threshold: parseInt(threshold[1], 10),
723
- window: window[1].replace(/,$/, ""),
724
- cooldown: cooldown[1].replace(/,$/, ""),
725
- };
726
- }
727
- return undefined;
728
- }
729
- // ---------------------------------------------------------------------------
730
- // Edge line parser
731
- // ---------------------------------------------------------------------------
732
- /**
733
- * Parse edge lines from the Execution Order section.
734
- *
735
- * Formats:
736
- * 1. from -> to
737
- * 1. from -> to when condition
738
- * 1. from -x-> to (max N iterations)
739
- * 1. from -> to (max N iterations) [error: type] [weight: N] [race] [tolerance: N] [wait: T] [reflection]
740
- */
741
- function parseEdgeLines(text) {
742
- const edges = [];
743
- const lines = text.split("\n").filter((l) => l.trim());
744
- for (const line of lines) {
745
- // Match: N. from ARROW to [rest]
746
- // Arrow is either -> or -x->
747
- const match = line.match(/^\d+\.\s+(\S+)\s+(->|-x->)\s+(\S+)\s*(.*)?$/);
748
- if (!match)
749
- continue;
750
- const from = match[1];
751
- const arrow = match[2];
752
- const to = match[3];
753
- const rest = match[4] ?? "";
754
- const edge = {
755
- from,
756
- to,
757
- condition: null,
758
- maxIterations: null,
759
- };
760
- if (arrow === "-x->") {
761
- edge.isError = true;
762
- }
763
- // when condition
764
- const whenMatch = rest.match(/when\s+(.+?)(?:\s*\(|$)/);
765
- if (whenMatch)
766
- edge.condition = whenMatch[1].trim();
767
- // (max N iterations)
768
- const maxMatch = rest.match(/\(max (\d+) iterations?\)/);
769
- if (maxMatch)
770
- edge.maxIterations = parseInt(maxMatch[1], 10);
771
- // [error: type]
772
- const errorTypeMatch = rest.match(/\[error:\s*(\S+)\]/);
773
- if (errorTypeMatch)
774
- edge.errorType = errorTypeMatch[1];
775
- // [weight: N]
776
- const weightMatch = rest.match(/\[weight:\s*([\d.]+)\]/);
777
- if (weightMatch)
778
- edge.weight = parseFloat(weightMatch[1]);
779
- // [race]
780
- if (/\[race\]/.test(rest))
781
- edge.race = true;
782
- // [tolerance: N]
783
- const tolMatch = rest.match(/\[tolerance:\s*([^\]]+)\]/);
784
- if (tolMatch) {
785
- const tVal = tolMatch[1].trim();
786
- edge.tolerance = tVal.includes("%") ? tVal : parseInt(tVal, 10);
787
- }
788
- // [wait: T]
789
- const waitMatch = rest.match(/\[wait:\s*(\S+)\]/);
790
- if (waitMatch)
791
- edge.wait = waitMatch[1];
792
- // [reflection]
793
- if (/\[reflection\]/.test(rest))
794
- edge.reflection = true;
795
- edges.push(edge);
796
- }
797
- return edges;
798
- }
799
- export function parseMemoryMd(content) {
800
- const memory = {};
801
- // ## Workspace or workspace subsection
802
- const workspaceSection = extractSection(content, "## Workspace") ??
803
- extractSection(content, "### Workspace");
804
- if (workspaceSection) {
805
- const workspace = {};
806
- const pathMatch = workspaceSection.match(/Path:\s*`?([^`\n]+)`?/);
807
- if (pathMatch)
808
- workspace.path = pathMatch[1].trim();
809
- const structMatch = workspaceSection.match(/Structure:\s*(.+)/);
810
- if (structMatch) {
811
- workspace.structure = structMatch[1].split(/,\s*/).map((s) => s.trim());
812
- }
813
- // Also handle bullet list structure
814
- const bulletStructure = workspaceSection.match(/Structure:\s*\n((?:\s+- .+\n?)+)/);
815
- if (bulletStructure) {
816
- workspace.structure = bulletStructure[1]
817
- .split("\n")
818
- .filter((l) => l.trim().startsWith("- "))
819
- .map((l) => l.trim().slice(2).trim());
820
- }
821
- memory.workspace = workspace;
822
- }
823
- // ## Domains or domains subsection
824
- const domainsSection = extractSection(content, "## Domains") ??
825
- extractSection(content, "### Domains");
826
- if (domainsSection) {
827
- const domains = {};
828
- const pathMatch = domainsSection.match(/Path:\s*`?([^`\n]+)`?/);
829
- if (pathMatch)
830
- domains.path = pathMatch[1].trim();
831
- const routingMatch = domainsSection.match(/Routing:\s*(.+)/);
832
- if (routingMatch)
833
- domains.routing = routingMatch[1].trim();
834
- memory.domains = domains;
835
- }
836
- return { memory };
837
- }
838
- // ---------------------------------------------------------------------------
839
- // Skill file parser
840
- // ---------------------------------------------------------------------------
841
- export function parseSkillFile(skillId, content) {
842
- const { fields, body } = parseFrontmatter(content);
843
- const skill = {
844
- id: skillId,
845
- description: fields.description ?? body.split("\n")[0]?.trim() ?? "",
846
- };
847
- if (fields["user-invocable"] !== undefined) {
848
- skill.userInvocable = fields["user-invocable"];
849
- }
850
- return skill;
851
- }
852
- // ---------------------------------------------------------------------------
853
- // cron/jobs.json parser (fallback)
854
- // ---------------------------------------------------------------------------
855
- export function parseCronJobsJson(content) {
856
- const json = JSON.parse(content);
857
- const jobs = [];
858
- const jobList = json.jobs ?? json;
859
- if (!Array.isArray(jobList))
860
- return jobs;
861
- for (const job of jobList) {
862
- const def = {
863
- id: job.id,
864
- enabled: job.enabled ?? true,
865
- };
866
- if (job.agentId)
867
- def.agent = job.agentId;
868
- if (job.schedule?.expr)
869
- def.cron = job.schedule.expr;
870
- if (job.cron)
871
- def.cron = job.cron;
872
- jobs.push(def);
873
- }
874
- return jobs;
875
- }
876
- // ---------------------------------------------------------------------------
877
- // Main import function
878
- // ---------------------------------------------------------------------------
879
- export function importOpenClaw(files, topologyName) {
880
- // 1. Parse openclaw.json
881
- let openclawJson = null;
882
- for (const file of files) {
883
- if (file.path.endsWith("openclaw.json") && !file.path.includes("node_modules")) {
884
- openclawJson = parseOpenClawJson(file.content);
885
- break;
886
- }
887
- }
888
- // Helper: match root-level markdown files (not inside config/agents/)
889
- const isRootMd = (path, name) => (path.endsWith(`/${name}`) || path === name) &&
890
- !path.includes("config/agents/");
891
- // 2. Parse root SOUL.md (not in config/agents/)
892
- let soulResult = null;
893
- for (const file of files) {
894
- if (isRootMd(file.path, "SOUL.md")) {
895
- soulResult = parseSoulMd(file.content);
896
- break;
897
- }
898
- }
899
- // 3. Parse root AGENTS.md (not in config/agents/)
900
- let agentsResult = null;
901
- for (const file of files) {
902
- if (isRootMd(file.path, "AGENTS.md")) {
903
- agentsResult = parseAgentsMd(file.content);
904
- break;
905
- }
906
- }
907
- // 4. Parse root MEMORY.md (not in config/agents/)
908
- let memoryResult = null;
909
- for (const file of files) {
910
- if (isRootMd(file.path, "MEMORY.md")) {
911
- memoryResult = parseMemoryMd(file.content);
912
- break;
913
- }
914
- }
915
- // 5. Parse skill files
916
- const skills = [];
917
- const skillPattern = /skills\/([^/]+)\/SKILL\.md$/;
918
- for (const file of files) {
919
- const match = skillPattern.exec(file.path);
920
- if (match) {
921
- skills.push(parseSkillFile(match[1], file.content));
922
- }
923
- }
924
- // 6. Parse cron/jobs.json (fallback for schedules)
925
- let cronJobs = [];
926
- for (const file of files) {
927
- if (file.path.endsWith("cron/jobs.json")) {
928
- try {
929
- cronJobs = parseCronJobsJson(file.content);
930
- }
931
- catch {
932
- // Ignore parse errors
933
- }
934
- break;
935
- }
936
- }
937
- // --- Merge everything into a TopologyAST ---
938
- const providerNames = openclawJson?.providerNames ?? [];
939
- // Reverse model mapping on agents
940
- const agents = agentsResult?.agents ?? [];
941
- if (openclawJson) {
942
- for (const agent of agents) {
943
- // Enrich from openclaw.json
944
- const jsonAgent = openclawJson.agents.find((a) => a.id === agent.id);
945
- if (jsonAgent) {
946
- // Prefer openclaw.json model (more structured)
947
- if (jsonAgent.model) {
948
- agent.model = reverseMapModel(jsonAgent.model, providerNames);
949
- }
950
- // Fallback chain
951
- if (jsonAgent.fallbacks && jsonAgent.fallbacks.length > 0) {
952
- agent.fallbackChain = jsonAgent.fallbacks.map((m) => reverseMapModel(m, providerNames));
953
- }
954
- }
955
- // If model wasn't in openclaw.json, still reverse-map the AGENTS.md model
956
- if (agent.model && !openclawJson.agents.find((a) => a.id === agent.id)?.model) {
957
- agent.model = reverseMapModel(agent.model, providerNames);
958
- }
959
- }
960
- }
961
- // Match agent roles to role keys if they match role descriptions
962
- const roles = soulResult?.roles ?? {};
963
- for (const agent of agents) {
964
- if (agent.role) {
965
- // Check if agent.role text matches a role description — if so, use the role key
966
- for (const [key, desc] of Object.entries(roles)) {
967
- if (agent.role === desc) {
968
- agent.role = key;
969
- break;
970
- }
971
- }
972
- }
973
- }
974
- // Build providers — reverse-map model IDs in provider model lists
975
- const providers = openclawJson?.providers ?? [];
976
- for (const provider of providers) {
977
- provider.models = provider.models.map((m) => reverseMapModel(m, providerNames));
978
- // Mark default provider
979
- if (openclawJson?.defaultModel) {
980
- const defaultModelProvider = openclawJson.defaultModel.split("/")[0];
981
- if (defaultModelProvider === provider.name) {
982
- provider.default = true;
983
- }
984
- }
985
- }
986
- // Build nodes array
987
- const nodes = [
988
- ...agents,
989
- ...(agentsResult?.groups ?? []),
990
- ...(agentsResult?.humans ?? []),
991
- ...(agentsResult?.gates ?? []),
992
- ];
993
- // Build topology meta
994
- const topology = {
995
- name: soulResult?.name ?? topologyName,
996
- version: soulResult?.version ?? "1.0.0",
997
- description: soulResult?.description ?? "",
998
- patterns: soulResult?.patterns ?? [],
999
- domain: soulResult?.domain,
1000
- timeout: soulResult?.timeout,
1001
- durable: soulResult?.durable,
1002
- errorHandler: soulResult?.errorHandler,
1003
- };
1004
- // Env — merge from SOUL.md and openclaw.json (SOUL.md takes priority)
1005
- const env = {
1006
- ...(openclawJson?.env ?? {}),
1007
- ...(soulResult?.env ?? {}),
1008
- };
1009
- // Settings
1010
- const settings = {};
1011
- if (openclawJson?.settings.allow?.length) {
1012
- settings.allow = openclawJson.settings.allow;
1013
- }
1014
- if (openclawJson?.settings.deny?.length) {
1015
- settings.deny = openclawJson.settings.deny;
1016
- }
1017
- // Schedules — prefer AGENTS.md, fallback to cron/jobs.json
1018
- const schedules = (agentsResult?.schedules?.length ?? 0) > 0
1019
- ? agentsResult.schedules
1020
- : cronJobs;
1021
- // Extensions
1022
- const extensions = openclawJson?.extensions && Object.keys(openclawJson.extensions).length > 0
1023
- ? { openclaw: openclawJson.extensions }
1024
- : undefined;
1025
- return {
1026
- topology,
1027
- nodes,
1028
- edges: agentsResult?.edges ?? [],
1029
- depth: { factors: [], levels: [] },
1030
- memory: memoryResult?.memory ?? {},
1031
- stores: [],
1032
- retrievals: [],
1033
- batch: {},
1034
- environments: {},
1035
- triggers: soulResult?.triggers ?? [],
1036
- hooks: agentsResult?.hooks ?? [],
1037
- settings,
1038
- mcpServers: {},
1039
- metering: agentsResult?.metering ?? null,
1040
- skills,
1041
- toolDefs: [],
1042
- roles,
1043
- context: {},
1044
- env,
1045
- providers,
1046
- schedules,
1047
- interfaces: [],
1048
- defaults: null,
1049
- schemas: [],
1050
- extensions,
1051
- observability: null,
1052
- params: soulResult?.params ?? [],
1053
- interfaceEndpoints: soulResult?.interfaceEndpoints ?? null,
1054
- imports: [],
1055
- includes: [],
1056
- checkpoint: null,
1057
- artifacts: [],
1058
- };
1059
- }
1060
- //# sourceMappingURL=openclaw.js.map