agentopology 0.1.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 (174) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +450 -0
  3. package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
  4. package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
  5. package/dist/analyzer/__tests__/analyzer.test.js +258 -0
  6. package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
  7. package/dist/analyzer/index.d.ts +44 -0
  8. package/dist/analyzer/index.d.ts.map +1 -0
  9. package/dist/analyzer/index.js +308 -0
  10. package/dist/analyzer/index.js.map +1 -0
  11. package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
  12. package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
  13. package/dist/bindings/__tests__/bindings.test.js +2089 -0
  14. package/dist/bindings/__tests__/bindings.test.js.map +1 -0
  15. package/dist/bindings/claude-code.d.ts +12 -0
  16. package/dist/bindings/claude-code.d.ts.map +1 -0
  17. package/dist/bindings/claude-code.js +2063 -0
  18. package/dist/bindings/claude-code.js.map +1 -0
  19. package/dist/bindings/codex.d.ts +18 -0
  20. package/dist/bindings/codex.d.ts.map +1 -0
  21. package/dist/bindings/codex.js +1633 -0
  22. package/dist/bindings/codex.js.map +1 -0
  23. package/dist/bindings/copilot-cli.d.ts +20 -0
  24. package/dist/bindings/copilot-cli.d.ts.map +1 -0
  25. package/dist/bindings/copilot-cli.js +1515 -0
  26. package/dist/bindings/copilot-cli.js.map +1 -0
  27. package/dist/bindings/cursor.d.ts +17 -0
  28. package/dist/bindings/cursor.d.ts.map +1 -0
  29. package/dist/bindings/cursor.js +1715 -0
  30. package/dist/bindings/cursor.js.map +1 -0
  31. package/dist/bindings/gemini-cli.d.ts +18 -0
  32. package/dist/bindings/gemini-cli.d.ts.map +1 -0
  33. package/dist/bindings/gemini-cli.js +1327 -0
  34. package/dist/bindings/gemini-cli.js.map +1 -0
  35. package/dist/bindings/index.d.ts +20 -0
  36. package/dist/bindings/index.d.ts.map +1 -0
  37. package/dist/bindings/index.js +33 -0
  38. package/dist/bindings/index.js.map +1 -0
  39. package/dist/bindings/kiro.d.ts +17 -0
  40. package/dist/bindings/kiro.d.ts.map +1 -0
  41. package/dist/bindings/kiro.js +1624 -0
  42. package/dist/bindings/kiro.js.map +1 -0
  43. package/dist/bindings/openclaw.d.ts +18 -0
  44. package/dist/bindings/openclaw.d.ts.map +1 -0
  45. package/dist/bindings/openclaw.js +1578 -0
  46. package/dist/bindings/openclaw.js.map +1 -0
  47. package/dist/bindings/types.d.ts +33 -0
  48. package/dist/bindings/types.d.ts.map +1 -0
  49. package/dist/bindings/types.js +23 -0
  50. package/dist/bindings/types.js.map +1 -0
  51. package/dist/cli/index.d.ts +19 -0
  52. package/dist/cli/index.d.ts.map +1 -0
  53. package/dist/cli/index.js +679 -0
  54. package/dist/cli/index.js.map +1 -0
  55. package/dist/docs/__tests__/docs.test.d.ts +2 -0
  56. package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
  57. package/dist/docs/__tests__/docs.test.js +438 -0
  58. package/dist/docs/__tests__/docs.test.js.map +1 -0
  59. package/dist/docs/content.d.ts +16 -0
  60. package/dist/docs/content.d.ts.map +1 -0
  61. package/dist/docs/content.js +3273 -0
  62. package/dist/docs/content.js.map +1 -0
  63. package/dist/docs/index.d.ts +17 -0
  64. package/dist/docs/index.d.ts.map +1 -0
  65. package/dist/docs/index.js +117 -0
  66. package/dist/docs/index.js.map +1 -0
  67. package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
  68. package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
  69. package/dist/exporters/__tests__/exporters.test.js +731 -0
  70. package/dist/exporters/__tests__/exporters.test.js.map +1 -0
  71. package/dist/exporters/index.d.ts +13 -0
  72. package/dist/exporters/index.d.ts.map +1 -0
  73. package/dist/exporters/index.js +18 -0
  74. package/dist/exporters/index.js.map +1 -0
  75. package/dist/exporters/json.d.ts +8 -0
  76. package/dist/exporters/json.d.ts.map +1 -0
  77. package/dist/exporters/json.js +25 -0
  78. package/dist/exporters/json.js.map +1 -0
  79. package/dist/exporters/markdown.d.ts +9 -0
  80. package/dist/exporters/markdown.d.ts.map +1 -0
  81. package/dist/exporters/markdown.js +686 -0
  82. package/dist/exporters/markdown.js.map +1 -0
  83. package/dist/exporters/mermaid.d.ts +11 -0
  84. package/dist/exporters/mermaid.d.ts.map +1 -0
  85. package/dist/exporters/mermaid.js +161 -0
  86. package/dist/exporters/mermaid.js.map +1 -0
  87. package/dist/exporters/types.d.ts +22 -0
  88. package/dist/exporters/types.d.ts.map +1 -0
  89. package/dist/exporters/types.js +10 -0
  90. package/dist/exporters/types.js.map +1 -0
  91. package/dist/import/__tests__/import.test.d.ts +2 -0
  92. package/dist/import/__tests__/import.test.d.ts.map +1 -0
  93. package/dist/import/__tests__/import.test.js +781 -0
  94. package/dist/import/__tests__/import.test.js.map +1 -0
  95. package/dist/import/claude-code.d.ts +38 -0
  96. package/dist/import/claude-code.d.ts.map +1 -0
  97. package/dist/import/claude-code.js +897 -0
  98. package/dist/import/claude-code.js.map +1 -0
  99. package/dist/import/index.d.ts +18 -0
  100. package/dist/import/index.d.ts.map +1 -0
  101. package/dist/import/index.js +28 -0
  102. package/dist/import/index.js.map +1 -0
  103. package/dist/import/serializer.d.ts +11 -0
  104. package/dist/import/serializer.d.ts.map +1 -0
  105. package/dist/import/serializer.js +960 -0
  106. package/dist/import/serializer.js.map +1 -0
  107. package/dist/index.d.ts +26 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +21 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/parser/__tests__/parser.test.d.ts +2 -0
  112. package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
  113. package/dist/parser/__tests__/parser.test.js +6514 -0
  114. package/dist/parser/__tests__/parser.test.js.map +1 -0
  115. package/dist/parser/ast.d.ts +708 -0
  116. package/dist/parser/ast.d.ts.map +1 -0
  117. package/dist/parser/ast.js +12 -0
  118. package/dist/parser/ast.js.map +1 -0
  119. package/dist/parser/index.d.ts +253 -0
  120. package/dist/parser/index.d.ts.map +1 -0
  121. package/dist/parser/index.js +1985 -0
  122. package/dist/parser/index.js.map +1 -0
  123. package/dist/parser/lexer.d.ts +100 -0
  124. package/dist/parser/lexer.d.ts.map +1 -0
  125. package/dist/parser/lexer.js +304 -0
  126. package/dist/parser/lexer.js.map +1 -0
  127. package/dist/parser/validator.d.ts +42 -0
  128. package/dist/parser/validator.d.ts.map +1 -0
  129. package/dist/parser/validator.js +2568 -0
  130. package/dist/parser/validator.js.map +1 -0
  131. package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
  132. package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
  133. package/dist/scaffold/__tests__/scaffold.test.js +295 -0
  134. package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
  135. package/dist/scaffold/incremental.d.ts +43 -0
  136. package/dist/scaffold/incremental.d.ts.map +1 -0
  137. package/dist/scaffold/incremental.js +185 -0
  138. package/dist/scaffold/incremental.js.map +1 -0
  139. package/dist/scaffold/index.d.ts +13 -0
  140. package/dist/scaffold/index.d.ts.map +1 -0
  141. package/dist/scaffold/index.js +13 -0
  142. package/dist/scaffold/index.js.map +1 -0
  143. package/dist/scaffold/manifest.d.ts +30 -0
  144. package/dist/scaffold/manifest.d.ts.map +1 -0
  145. package/dist/scaffold/manifest.js +71 -0
  146. package/dist/scaffold/manifest.js.map +1 -0
  147. package/dist/scaffold/merge.d.ts +52 -0
  148. package/dist/scaffold/merge.d.ts.map +1 -0
  149. package/dist/scaffold/merge.js +134 -0
  150. package/dist/scaffold/merge.js.map +1 -0
  151. package/dist/scaffold/types.d.ts +51 -0
  152. package/dist/scaffold/types.d.ts.map +1 -0
  153. package/dist/scaffold/types.js +10 -0
  154. package/dist/scaffold/types.js.map +1 -0
  155. package/dist/sync/index.d.ts +34 -0
  156. package/dist/sync/index.d.ts.map +1 -0
  157. package/dist/sync/index.js +392 -0
  158. package/dist/sync/index.js.map +1 -0
  159. package/dist/visualizer/index.d.ts +17 -0
  160. package/dist/visualizer/index.d.ts.map +1 -0
  161. package/dist/visualizer/index.js +1976 -0
  162. package/dist/visualizer/index.js.map +1 -0
  163. package/examples/code-review.at +167 -0
  164. package/examples/data-processing.at +296 -0
  165. package/examples/openclaw-assistant.at +142 -0
  166. package/examples/scheduled-monitor.at +193 -0
  167. package/examples/simple-pipeline.at +163 -0
  168. package/package.json +77 -0
  169. package/skill/SKILL.md +403 -0
  170. package/skill/scripts/emit.ts +853 -0
  171. package/skill/scripts/visualize.ts +89 -0
  172. package/spec/grammar.md +1466 -0
  173. package/spec/reserved-keywords.md +147 -0
  174. package/spec/validation.md +535 -0
@@ -0,0 +1,1578 @@
1
+ /**
2
+ * OpenClaw binding.
3
+ *
4
+ * Generates a complete OpenClaw workspace from a parsed {@link TopologyAST}:
5
+ * openclaw.json, SOUL.md, AGENTS.md, TOOLS.md, MEMORY.md, BOOTSTRAP.md,
6
+ * USER.md, IDENTITY.md, skill directories, gate/hook scripts, and workspace structure.
7
+ *
8
+ * OpenClaw is a multi-channel AI agent framework with gateway, channels,
9
+ * and markdown-based agent identity. This binding adds security hardening
10
+ * (auth required by default), quality gates, bounded loops, structured
11
+ * metrics, and workspace access control that OpenClaw lacks natively.
12
+ *
13
+ * @module
14
+ */
15
+ import { deduplicateFiles } from "./types.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+ /** Convert a kebab-case id to Title Case. */
20
+ function toTitle(id) {
21
+ return id
22
+ .split(/[-_]/)
23
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
24
+ .join(" ");
25
+ }
26
+ /**
27
+ * Generate a human-readable model alias from a fully-qualified model string.
28
+ * E.g. "openrouter/google/gemini-2.5-flash" → "Gemini 2.5 Flash"
29
+ * "openrouter/anthropic/claude-sonnet-4-6" → "Claude Sonnet 4.6"
30
+ */
31
+ function modelAlias(model) {
32
+ // Extract last segment after the last "/"
33
+ const lastSlash = model.lastIndexOf("/");
34
+ const baseName = lastSlash >= 0 ? model.slice(lastSlash + 1) : model;
35
+ // Title-case each hyphen-separated word
36
+ const words = baseName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1));
37
+ // Join and convert version-like patterns: "4 6" → "4.6", "2 5" → "2.5"
38
+ return words.join(" ").replace(/(\d+)\s+(\d+)/g, "$1.$2");
39
+ }
40
+ /** Generate a shell script stub with a header comment. */
41
+ function shellStub(description) {
42
+ return [
43
+ "#!/usr/bin/env bash",
44
+ `# ${description}`,
45
+ "# Auto-generated by agentopology scaffold — edit as needed.",
46
+ "set -euo pipefail",
47
+ "",
48
+ 'echo "TODO: implement this script"',
49
+ "",
50
+ ].join("\n");
51
+ }
52
+ /** Generate an empty directory marker (a .gitkeep file). */
53
+ function gitkeep(dirPath) {
54
+ return { path: `${dirPath}/.gitkeep`, content: "", category: "machine" };
55
+ }
56
+ /**
57
+ * Map topology model strings to OpenClaw model identifiers.
58
+ * OpenClaw uses `provider/model-id` format with hyphens.
59
+ */
60
+ function mapModel(model, defaultProvider) {
61
+ const provider = defaultProvider ?? "anthropic";
62
+ const MODEL_MAP = {
63
+ opus: `${provider}/claude-opus-4-6`,
64
+ sonnet: `${provider}/claude-sonnet-4-6`,
65
+ haiku: `${provider}/claude-haiku-4-5`,
66
+ };
67
+ // If model already has provider prefix, return as-is
68
+ if (model.includes("/"))
69
+ return model;
70
+ return MODEL_MAP[model] ?? `${provider}/${model}`;
71
+ }
72
+ /**
73
+ * Map topology permission strings to OpenClaw permission labels.
74
+ */
75
+ function mapPermissions(perm) {
76
+ switch (perm) {
77
+ case "auto":
78
+ return "autonomous";
79
+ case "plan":
80
+ return "supervised";
81
+ case "confirm":
82
+ return "interactive";
83
+ case "bypass":
84
+ case "bypassPermissions":
85
+ return "unrestricted";
86
+ case "supervised":
87
+ case "autonomous":
88
+ case "interactive":
89
+ case "unrestricted":
90
+ return perm;
91
+ default:
92
+ return "autonomous";
93
+ }
94
+ }
95
+ /**
96
+ * Extract openclaw-specific extension fields from the topology.
97
+ * Top-level `extensions { openclaw { ... } }` lives on `ast.extensions`.
98
+ */
99
+ function getOpenClawExtensions(ast) {
100
+ // Top-level extensions block (ast.extensions.openclaw)
101
+ const topExt = ast.extensions;
102
+ if (topExt?.openclaw)
103
+ return topExt.openclaw;
104
+ // Legacy fallback: settings.extensions
105
+ const settingsExt = ast.settings?.extensions;
106
+ if (settingsExt?.openclaw)
107
+ return settingsExt.openclaw;
108
+ return null;
109
+ }
110
+ /** Get the orchestrator node from AST nodes. */
111
+ function getOrchestrator(ast) {
112
+ return ast.nodes.find((n) => n.type === "orchestrator");
113
+ }
114
+ /** Get all agent nodes from AST, sorted by phase. */
115
+ function getAgentsSorted(ast) {
116
+ const agents = ast.nodes.filter((n) => n.type === "agent");
117
+ return agents.sort((a, b) => (a.phase ?? 0) - (b.phase ?? 0));
118
+ }
119
+ /** Get all gate nodes from AST. */
120
+ function getGates(ast) {
121
+ return ast.nodes.filter((n) => n.type === "gate");
122
+ }
123
+ /** Get all human nodes from AST. */
124
+ function getHumanNodes(ast) {
125
+ return ast.nodes.filter((n) => n.type === "human");
126
+ }
127
+ /** Get all group nodes from AST. */
128
+ function getGroupNodes(ast) {
129
+ return ast.nodes.filter((n) => n.type === "group");
130
+ }
131
+ /** Format a schema field for documentation. */
132
+ function formatSchemaField(field) {
133
+ const opt = field.optional ? "?" : "";
134
+ const typeStr = formatSchemaType(field.type);
135
+ return `${field.name}${opt}: ${typeStr}`;
136
+ }
137
+ /** Format a schema type expression to a string. */
138
+ function formatSchemaType(t) {
139
+ switch (t.kind) {
140
+ case "primitive":
141
+ return t.value;
142
+ case "array":
143
+ return `${formatSchemaType(t.itemType)}[]`;
144
+ case "enum":
145
+ return t.values.join(" | ");
146
+ case "ref":
147
+ return t.name;
148
+ }
149
+ }
150
+ /** Format retry config for documentation. */
151
+ function formatRetry(retry) {
152
+ if (typeof retry === "number")
153
+ return `max ${retry} attempts`;
154
+ const parts = [`max ${retry.max} attempts`];
155
+ if (retry.backoff)
156
+ parts.push(`backoff: ${retry.backoff}`);
157
+ if (retry.interval)
158
+ parts.push(`interval: ${retry.interval}`);
159
+ if (retry.maxInterval)
160
+ parts.push(`max interval: ${retry.maxInterval}`);
161
+ if (retry.jitter)
162
+ parts.push("jitter: on");
163
+ if (retry.nonRetryable && retry.nonRetryable.length > 0) {
164
+ parts.push(`non-retryable: ${retry.nonRetryable.join(", ")}`);
165
+ }
166
+ return parts.join(", ");
167
+ }
168
+ // ---------------------------------------------------------------------------
169
+ // File generators
170
+ // ---------------------------------------------------------------------------
171
+ /** Get the default provider name from the AST providers list. */
172
+ function getDefaultProviderName(ast) {
173
+ if (ast.providers && ast.providers.length > 0) {
174
+ const defaultProv = ast.providers.find((p) => p.default);
175
+ if (defaultProv)
176
+ return defaultProv.name;
177
+ return ast.providers[0].name;
178
+ }
179
+ return "anthropic";
180
+ }
181
+ /** Generate openclaw.json — central system config. */
182
+ function generateOpenClawJson(ast) {
183
+ const ext = getOpenClawExtensions(ast);
184
+ const orchestrator = getOrchestrator(ast);
185
+ const agents = getAgentsSorted(ast);
186
+ const defaultProvider = getDefaultProviderName(ast);
187
+ // Build agent list for config
188
+ const agentList = [];
189
+ // Determine default model for agents.defaults
190
+ // If the orchestrator/first-agent model already has a provider prefix, use it
191
+ // as-is (mapModel already passes through slash-containing strings).
192
+ const defaultModel = orchestrator
193
+ ? mapModel(orchestrator.model, defaultProvider)
194
+ : agents.length > 0
195
+ ? mapModel(agents[0].model ?? "sonnet", defaultProvider)
196
+ : mapModel("sonnet", defaultProvider);
197
+ // Orchestrator as primary agent
198
+ if (orchestrator) {
199
+ const agentIds = agents.map((a) => a.id);
200
+ const orchModel = {
201
+ primary: mapModel(orchestrator.model, defaultProvider),
202
+ };
203
+ agentList.push({
204
+ id: "orchestrator",
205
+ name: toTitle(ast.topology.name) + " Coordinator",
206
+ model: orchModel,
207
+ workspace: `~/.openclaw/workspace-${ast.topology.name}`,
208
+ agentDir: `./config/agents/orchestrator/agent`,
209
+ subagents: {
210
+ allowAgents: agentIds,
211
+ },
212
+ });
213
+ }
214
+ // Each agent node
215
+ for (const agent of agents) {
216
+ const agentModel = {
217
+ primary: mapModel(agent.model ?? "sonnet", defaultProvider),
218
+ };
219
+ // Per-agent fallback chain goes into model.fallbacks
220
+ if (agent.fallbackChain && agent.fallbackChain.length > 0) {
221
+ agentModel.fallbacks = agent.fallbackChain.map((m) => mapModel(m, defaultProvider));
222
+ }
223
+ const entry = {
224
+ id: agent.id,
225
+ name: toTitle(agent.id),
226
+ model: agentModel,
227
+ workspace: `~/.openclaw/workspace-${agent.id}`,
228
+ agentDir: `./config/agents/${agent.id}/agent`,
229
+ };
230
+ // No per-agent tools in openclaw.json — tool restrictions go in AGENTS.md
231
+ // Sampling params
232
+ if (agent.temperature != null)
233
+ entry.temperature = agent.temperature;
234
+ if (agent.maxTokens != null)
235
+ entry.maxTokens = agent.maxTokens;
236
+ // Timeout
237
+ if (agent.timeout)
238
+ entry.timeout = agent.timeout;
239
+ // Merge agent-level openclaw extensions
240
+ if (agent.extensions?.openclaw) {
241
+ for (const [k, v] of Object.entries(agent.extensions.openclaw)) {
242
+ entry[k] = v;
243
+ }
244
+ }
245
+ agentList.push(entry);
246
+ }
247
+ // Build fallback chain for agents.defaults.model
248
+ let defaultFallbacks;
249
+ const settingsFallback = ast.settings?.fallbackChain;
250
+ if (settingsFallback && settingsFallback.length > 0) {
251
+ defaultFallbacks = settingsFallback.map((m) => mapModel(m, defaultProvider));
252
+ }
253
+ else if (ext?.["model-fallback-chain"]) {
254
+ const chain = ext["model-fallback-chain"];
255
+ defaultFallbacks = chain.map((m) => mapModel(m, defaultProvider));
256
+ }
257
+ // Build agents section with defaults
258
+ const agentsDefaults = {
259
+ workspace: "~/.openclaw/workspace",
260
+ model: defaultFallbacks
261
+ ? { primary: defaultModel, fallbacks: defaultFallbacks }
262
+ : { primary: defaultModel },
263
+ };
264
+ // Build config object
265
+ // Note: OpenClaw does NOT accept "name" or "version" at root level
266
+ const config = {
267
+ agents: {
268
+ defaults: agentsDefaults,
269
+ list: agentList,
270
+ },
271
+ gateway: {
272
+ port: ext?.["gateway-port"] ?? 18789,
273
+ auth: {
274
+ mode: "token",
275
+ token: (ext?.["auth-tokens"] ?? ["${OPENCLAW_AUTH_TOKEN}"])[0],
276
+ },
277
+ },
278
+ };
279
+ // First-class interfaces support — channels as named keys
280
+ if (ast.interfaces.length > 0) {
281
+ const channelConfig = {};
282
+ for (const iface of ast.interfaces) {
283
+ channelConfig[iface.id] = {
284
+ enabled: true,
285
+ type: iface.type,
286
+ ...iface.config,
287
+ };
288
+ }
289
+ config.channels = channelConfig;
290
+ }
291
+ else if (ext?.channels) {
292
+ // Extension fallback — channels as named keys
293
+ // ext.channels can be a real array or a parser-produced string like "[telegram]"
294
+ let channelNames;
295
+ if (Array.isArray(ext.channels)) {
296
+ channelNames = ext.channels;
297
+ }
298
+ else if (typeof ext.channels === "string") {
299
+ // Parse "[telegram, slack]" or "[telegram]" string format
300
+ const raw = ext.channels.replace(/^\[|\]$/g, "").trim();
301
+ channelNames = raw ? raw.split(/\s*,\s*/) : [];
302
+ }
303
+ else {
304
+ channelNames = [];
305
+ }
306
+ if (channelNames.length > 0) {
307
+ const channelConfig = {};
308
+ for (const ch of channelNames) {
309
+ const chConfig = { enabled: true };
310
+ // Channel config may be a nested object or flattened into ext
311
+ const chData = ext[ch];
312
+ if (chData && typeof chData === "object" && chData !== null && String(chData) !== "{") {
313
+ const chExt = chData;
314
+ // Map known channel fields
315
+ if (ch === "telegram") {
316
+ if (chExt["bot-token"])
317
+ chConfig.botToken = chExt["bot-token"];
318
+ if (chExt["dm-policy"])
319
+ chConfig.dmPolicy = chExt["dm-policy"];
320
+ }
321
+ else if (ch === "slack") {
322
+ if (chExt["app-token"])
323
+ chConfig.appToken = chExt["app-token"];
324
+ if (chExt["bot-token"])
325
+ chConfig.botToken = chExt["bot-token"];
326
+ }
327
+ // Copy any remaining fields
328
+ for (const [k, v] of Object.entries(chExt)) {
329
+ const camelKey = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
330
+ if (!(camelKey in chConfig)) {
331
+ chConfig[camelKey] = v;
332
+ }
333
+ }
334
+ }
335
+ else {
336
+ // Parser flattened the channel config into ext — look for known fields
337
+ if (ch === "telegram") {
338
+ if (ext["bot-token"])
339
+ chConfig.botToken = ext["bot-token"];
340
+ if (ext["dm-policy"])
341
+ chConfig.dmPolicy = ext["dm-policy"];
342
+ }
343
+ else if (ch === "slack") {
344
+ if (ext["app-token"])
345
+ chConfig.appToken = ext["app-token"];
346
+ if (ext["bot-token"])
347
+ chConfig.botToken = ext["bot-token"];
348
+ }
349
+ }
350
+ channelConfig[ch] = chConfig;
351
+ }
352
+ config.channels = channelConfig;
353
+ }
354
+ }
355
+ // Cron — just enable the system; job definitions go in cron/jobs.json
356
+ if (ast.schedules.length > 0 || ext?.["cron-jobs"]) {
357
+ config.cron = { enabled: true };
358
+ }
359
+ // Plugins
360
+ if (ext?.plugins) {
361
+ config.plugins = ext.plugins;
362
+ }
363
+ // Providers — models.providers object (not top-level array)
364
+ const modelsProviders = {};
365
+ if (ast.providers && ast.providers.length > 0) {
366
+ for (const p of ast.providers) {
367
+ const provEntry = {};
368
+ if (p.apiKey)
369
+ provEntry.apiKey = p.apiKey;
370
+ if (p.baseUrl)
371
+ provEntry.baseUrl = p.baseUrl;
372
+ // OpenClaw expects models as an array of objects with at minimum { id, name }
373
+ if (p.models.length > 0) {
374
+ provEntry.models = p.models.map((m) => {
375
+ const mapped = mapModel(m, p.name);
376
+ return { id: mapped, name: modelAlias(m) };
377
+ });
378
+ }
379
+ // Note: OpenClaw does NOT accept "default" on providers
380
+ // Provider auth
381
+ if (p.auth) {
382
+ const authConfig = { type: p.auth.type };
383
+ if (p.auth.issuer)
384
+ authConfig.issuer = p.auth.issuer;
385
+ if (p.auth.audience)
386
+ authConfig.audience = p.auth.audience;
387
+ if (p.auth.tokenUrl)
388
+ authConfig.tokenUrl = p.auth.tokenUrl;
389
+ if (p.auth.clientId)
390
+ authConfig.clientId = p.auth.clientId;
391
+ if (p.auth.scopes && p.auth.scopes.length > 0)
392
+ authConfig.scopes = p.auth.scopes;
393
+ provEntry.auth = authConfig;
394
+ }
395
+ // Merge extra fields
396
+ for (const [k, v] of Object.entries(p.extra)) {
397
+ provEntry[k] = v;
398
+ }
399
+ modelsProviders[p.name] = provEntry;
400
+ }
401
+ }
402
+ else {
403
+ // Default anthropic provider if none specified
404
+ modelsProviders.anthropic = {
405
+ apiKey: "${ANTHROPIC_API_KEY}",
406
+ };
407
+ }
408
+ config.models = { providers: modelsProviders };
409
+ // Note: OpenClaw does NOT support MCP servers. MCP tools should be
410
+ // implemented as OpenClaw skills instead. ast.mcpServers is intentionally
411
+ // not emitted into openclaw.json.
412
+ // Tools settings — flat allow/deny, no "defaults" nesting
413
+ const settings = ast.settings;
414
+ config.tools = {
415
+ allow: settings?.allow ?? [],
416
+ deny: settings?.deny ?? [],
417
+ agentToAgent: { enabled: true },
418
+ };
419
+ // Environment variables
420
+ if (Object.keys(ast.env).length > 0) {
421
+ const envBlock = {};
422
+ for (const [k, v] of Object.entries(ast.env)) {
423
+ envBlock[k] = typeof v === "string" ? v : v.value;
424
+ }
425
+ config.env = envBlock;
426
+ }
427
+ // Channel bindings — route channel messages to the default/first agent
428
+ if (config.channels) {
429
+ const channelNames = Object.keys(config.channels);
430
+ const defaultAgentId = orchestrator
431
+ ? "orchestrator"
432
+ : agents.length > 0
433
+ ? agents[0].id
434
+ : "default";
435
+ config.bindings = channelNames.map((ch) => ({
436
+ agentId: defaultAgentId,
437
+ match: { channel: ch },
438
+ }));
439
+ }
440
+ return {
441
+ path: "openclaw.json",
442
+ content: JSON.stringify(config, null, 2) + "\n",
443
+ category: "machine",
444
+ };
445
+ }
446
+ /** Generate cron/jobs.json — scheduled job definitions for OpenClaw. */
447
+ function generateCronJobsJson(ast) {
448
+ const ext = getOpenClawExtensions(ast);
449
+ const jobs = [];
450
+ if (ast.schedules.length > 0) {
451
+ for (const job of ast.schedules) {
452
+ const expr = job.cron ?? job.every ?? "";
453
+ jobs.push({
454
+ id: job.id,
455
+ name: toTitle(job.id),
456
+ agentId: job.agent ?? "default",
457
+ enabled: job.enabled !== false,
458
+ schedule: { kind: "cron", expr },
459
+ payload: { kind: "agentTurn", message: `Run ${job.id.replace(/-/g, " ")}` },
460
+ sessionTarget: "isolated",
461
+ });
462
+ }
463
+ }
464
+ else if (ext?.["cron-jobs"]) {
465
+ // Legacy extension fallback — pass through
466
+ return {
467
+ path: "cron/jobs.json",
468
+ content: JSON.stringify({ jobs: ext["cron-jobs"] }, null, 2) + "\n",
469
+ category: "machine",
470
+ };
471
+ }
472
+ if (jobs.length === 0)
473
+ return null;
474
+ return {
475
+ path: "cron/jobs.json",
476
+ content: JSON.stringify({ jobs }, null, 2) + "\n",
477
+ category: "machine",
478
+ };
479
+ }
480
+ /** Generate SOUL.md — primary agent identity. */
481
+ function generateSoulMd(ast) {
482
+ const orchestrator = getOrchestrator(ast);
483
+ const sections = [];
484
+ // Quote block with description
485
+ if (ast.topology.description) {
486
+ sections.push(`> ${ast.topology.description}`);
487
+ sections.push("");
488
+ }
489
+ // Identity
490
+ sections.push("## Identity");
491
+ sections.push(`- Name: ${toTitle(ast.topology.name)}`);
492
+ sections.push(`- Version: ${ast.topology.version}`);
493
+ if (orchestrator) {
494
+ sections.push(`- Model: ${mapModel(orchestrator.model)}`);
495
+ }
496
+ if (ast.topology.patterns.length > 0) {
497
+ sections.push(`- Patterns: ${ast.topology.patterns.join(", ")}`);
498
+ }
499
+ sections.push("");
500
+ // Mission
501
+ sections.push("## Mission");
502
+ const mission = ast.topology.description ?? "Coordinate agent topology.";
503
+ sections.push(mission);
504
+ sections.push("");
505
+ // Roles
506
+ if (Object.keys(ast.roles).length > 0) {
507
+ sections.push("## Roles");
508
+ for (const [roleName, roleDesc] of Object.entries(ast.roles)) {
509
+ sections.push(`### ${toTitle(roleName)}`);
510
+ sections.push(roleDesc);
511
+ sections.push("");
512
+ }
513
+ }
514
+ // Ethical guardrails
515
+ sections.push("## Ethical Guardrails");
516
+ if (orchestrator) {
517
+ sections.push(`- Permission model: ${mapPermissions(orchestrator.permissions ?? "auto")}`);
518
+ }
519
+ const deny = ast.settings?.deny;
520
+ sections.push(`- Tool restrictions: ${deny && deny.length > 0 ? deny.join(", ") : "none"}`);
521
+ sections.push("");
522
+ // Observability
523
+ if (ast.observability) {
524
+ const obs = ast.observability;
525
+ sections.push("## Observability");
526
+ sections.push(`- Enabled: ${obs.enabled}`);
527
+ sections.push(`- Level: ${obs.level}`);
528
+ sections.push(`- Exporter: ${obs.exporter}`);
529
+ if (obs.endpoint)
530
+ sections.push(`- Endpoint: ${obs.endpoint}`);
531
+ if (obs.service)
532
+ sections.push(`- Service: ${obs.service}`);
533
+ sections.push(`- Sample rate: ${obs.sampleRate}`);
534
+ sections.push(`- Capture: prompts=${obs.capture.prompts}, completions=${obs.capture.completions}, toolArgs=${obs.capture.toolArgs}, toolResults=${obs.capture.toolResults}`);
535
+ sections.push(`- Spans: agents=${obs.spans.agents}, tools=${obs.spans.tools}, gates=${obs.spans.gates}, memory=${obs.spans.memory}`);
536
+ sections.push("");
537
+ }
538
+ // Checkpoint
539
+ if (ast.checkpoint) {
540
+ const cp = ast.checkpoint;
541
+ sections.push("## Checkpoint / Durable Execution");
542
+ sections.push(`- Backend: ${cp.backend}`);
543
+ if (cp.connection)
544
+ sections.push(`- Connection: ${cp.connection}`);
545
+ sections.push(`- Strategy: ${cp.strategy}`);
546
+ if (cp.ttl)
547
+ sections.push(`- TTL: ${cp.ttl}`);
548
+ if (cp.replay) {
549
+ sections.push(`- Replay: enabled=${cp.replay.enabled}`);
550
+ if (cp.replay.maxHistory != null)
551
+ sections.push(` - Max history: ${cp.replay.maxHistory}`);
552
+ if (cp.replay.branch != null)
553
+ sections.push(` - Branch: ${cp.replay.branch}`);
554
+ }
555
+ sections.push("");
556
+ }
557
+ // Artifacts registry
558
+ if (ast.artifacts.length > 0) {
559
+ sections.push("## Artifacts");
560
+ for (const art of ast.artifacts) {
561
+ let line = `- **${art.id}** (${art.type})`;
562
+ if (art.path)
563
+ line += ` — path: ${art.path}`;
564
+ if (art.retention)
565
+ line += ` — retention: ${art.retention}`;
566
+ sections.push(line);
567
+ if (art.dependsOn && art.dependsOn.length > 0) {
568
+ sections.push(` - Depends on: ${art.dependsOn.join(", ")}`);
569
+ }
570
+ }
571
+ sections.push("");
572
+ }
573
+ // Depth levels
574
+ if (ast.depth && ast.depth.levels && ast.depth.levels.length > 0) {
575
+ sections.push("## Depth Levels");
576
+ sections.push("");
577
+ if (ast.depth.factors.length > 0) {
578
+ sections.push(`Factors: ${ast.depth.factors.join(", ")}`);
579
+ }
580
+ for (const level of ast.depth.levels) {
581
+ let line = `- **Level ${level.level}**: ${level.label}`;
582
+ if (level.omit.length > 0)
583
+ line += ` (omit: ${level.omit.join(", ")})`;
584
+ sections.push(line);
585
+ }
586
+ sections.push("");
587
+ }
588
+ // Composition: params
589
+ if (ast.params && ast.params.length > 0) {
590
+ sections.push("## Parameters");
591
+ sections.push("");
592
+ for (const p of ast.params) {
593
+ const req = p.required ? " (required)" : "";
594
+ const def = p.default != null ? ` = ${p.default}` : "";
595
+ sections.push(`- **${p.name}**: ${p.type}${req}${def}`);
596
+ }
597
+ sections.push("");
598
+ }
599
+ // Composition: interface endpoints
600
+ if (ast.interfaceEndpoints) {
601
+ sections.push("## Interface");
602
+ sections.push("");
603
+ sections.push(`- Entry: ${ast.interfaceEndpoints.entry}`);
604
+ sections.push(`- Exit: ${ast.interfaceEndpoints.exit}`);
605
+ sections.push("");
606
+ }
607
+ // Composition: imports
608
+ if (ast.imports && ast.imports.length > 0) {
609
+ sections.push("## Imports");
610
+ sections.push("");
611
+ for (const imp of ast.imports) {
612
+ let line = `- **${imp.alias}** from \`${imp.source}\``;
613
+ if (imp.sha256)
614
+ line += ` (sha256: ${imp.sha256})`;
615
+ if (imp.registry)
616
+ line += ` [registry: ${imp.registryPackage}@${imp.registryVersion}]`;
617
+ sections.push(line);
618
+ if (Object.keys(imp.params).length > 0) {
619
+ for (const [k, v] of Object.entries(imp.params)) {
620
+ sections.push(` - ${k}: ${v}`);
621
+ }
622
+ }
623
+ }
624
+ sections.push("");
625
+ }
626
+ // Composition: includes
627
+ if (ast.includes && ast.includes.length > 0) {
628
+ sections.push("## Includes");
629
+ sections.push("");
630
+ for (const inc of ast.includes) {
631
+ sections.push(`- ${inc.source}`);
632
+ }
633
+ sections.push("");
634
+ }
635
+ // Triggers
636
+ if (ast.triggers.length > 0) {
637
+ sections.push("## Triggers");
638
+ sections.push("");
639
+ for (const trigger of ast.triggers) {
640
+ sections.push(`### ${trigger.name}`);
641
+ sections.push(`Pattern: \`${trigger.pattern}\``);
642
+ if (trigger.argument)
643
+ sections.push(`Argument: ${trigger.argument}`);
644
+ sections.push("");
645
+ }
646
+ }
647
+ // Environment variables
648
+ if (Object.keys(ast.env).length > 0) {
649
+ sections.push("## Environment");
650
+ sections.push("");
651
+ for (const [key, val] of Object.entries(ast.env)) {
652
+ const value = typeof val === "string" ? val : val.value;
653
+ sections.push(`- \`${key}\`: ${value}`);
654
+ }
655
+ sections.push("");
656
+ }
657
+ return {
658
+ path: "SOUL.md",
659
+ content: sections.join("\n") + "\n",
660
+ category: "agent",
661
+ };
662
+ }
663
+ /** Generate AGENTS.md — sub-agent definitions + flow orchestration logic. */
664
+ function generateAgentsMd(ast) {
665
+ const agents = getAgentsSorted(ast);
666
+ const gates = getGates(ast);
667
+ const sections = [];
668
+ // Header
669
+ sections.push("# Sub-Agent Definitions");
670
+ sections.push("");
671
+ // Pipeline overview
672
+ sections.push("## Pipeline Overview");
673
+ sections.push(`${toTitle(ast.topology.name)} uses a ${ast.topology.patterns.join(" + ")} topology.`);
674
+ sections.push("");
675
+ // Agents
676
+ if (agents.length > 0) {
677
+ sections.push("## Agents");
678
+ sections.push("");
679
+ for (const agent of agents) {
680
+ sections.push(`### ${toTitle(agent.id)}`);
681
+ sections.push(`- **Model:** ${mapModel(agent.model ?? "sonnet")}`);
682
+ if (agent.phase != null) {
683
+ sections.push(`- **Phase:** ${agent.phase}`);
684
+ }
685
+ // Role
686
+ const roleText = ast.roles[agent.role ?? ""] ?? ast.roles[agent.id] ?? agent.role;
687
+ if (roleText) {
688
+ sections.push(`- **Role:** ${roleText}`);
689
+ }
690
+ // Tools (allowed/denied — enforced via AGENTS.md since openclaw.json doesn't support per-agent tools)
691
+ if (agent.tools && agent.tools.length > 0) {
692
+ sections.push(`- **Tools allowed:** ${agent.tools.join(", ")}`);
693
+ }
694
+ if (agent.disallowedTools && agent.disallowedTools.length > 0) {
695
+ sections.push(`- **Tools denied:** ${agent.disallowedTools.join(", ")}`);
696
+ }
697
+ else if (agent.tools && agent.tools.length > 0) {
698
+ sections.push(`- **Tools denied:** none`);
699
+ }
700
+ // Memory access
701
+ if (agent.reads && agent.reads.length > 0) {
702
+ sections.push(`- **Reads:** ${agent.reads.join(", ")}`);
703
+ }
704
+ if (agent.writes && agent.writes.length > 0) {
705
+ sections.push(`- **Writes:** ${agent.writes.join(", ")}`);
706
+ }
707
+ // Outputs
708
+ if (agent.outputs) {
709
+ sections.push("- **Outputs:**");
710
+ for (const [field, values] of Object.entries(agent.outputs)) {
711
+ sections.push(` - ${field}: ${values.join(" | ")}`);
712
+ }
713
+ }
714
+ // Scale
715
+ if (agent.scale) {
716
+ sections.push(`- **Scale:** ${agent.scale.min}-${agent.scale.max} instances by ${agent.scale.by}`);
717
+ }
718
+ // Permissions
719
+ if (agent.permissions) {
720
+ sections.push(`- **Permissions:** ${agent.permissions}`);
721
+ }
722
+ // Max turns
723
+ if (agent.maxTurns != null) {
724
+ sections.push(`- **Max turns:** ${agent.maxTurns}`);
725
+ }
726
+ // Skills
727
+ if (agent.skills && agent.skills.length > 0) {
728
+ sections.push(`- **Skills:** ${agent.skills.join(", ")}`);
729
+ }
730
+ // Description
731
+ if (agent.description) {
732
+ sections.push("");
733
+ sections.push(agent.description);
734
+ }
735
+ if (agent.prompt) {
736
+ sections.push("");
737
+ sections.push("#### Instructions");
738
+ sections.push("");
739
+ sections.push(agent.prompt);
740
+ }
741
+ // Timeout
742
+ if (agent.timeout) {
743
+ sections.push(`- **Maximum execution time:** ${agent.timeout}`);
744
+ }
745
+ // On-fail
746
+ if (agent.onFail) {
747
+ sections.push(`- **On failure:** ${agent.onFail}`);
748
+ }
749
+ // Retry
750
+ if (agent.retry != null) {
751
+ sections.push(`- **Retry:** ${formatRetry(agent.retry)}`);
752
+ }
753
+ // Thinking
754
+ if (agent.thinking) {
755
+ sections.push(`- **Reasoning level:** ${agent.thinking}`);
756
+ }
757
+ // Output format
758
+ if (agent.outputFormat) {
759
+ sections.push(`- **Output format:** ${agent.outputFormat}`);
760
+ }
761
+ // Log level
762
+ if (agent.logLevel) {
763
+ sections.push(`- **Log verbosity:** ${agent.logLevel}`);
764
+ }
765
+ // Join
766
+ if (agent.join) {
767
+ sections.push(`- **Wait for:** ${agent.join}`);
768
+ }
769
+ // Circuit breaker
770
+ if (agent.circuitBreaker) {
771
+ const cb = agent.circuitBreaker;
772
+ sections.push(`- **Circuit breaker:** threshold=${cb.threshold}, window=${cb.window}, cooldown=${cb.cooldown}`);
773
+ }
774
+ // Compensation
775
+ if (agent.compensates) {
776
+ sections.push(`- **Compensates:** ${agent.compensates} (saga rollback)`);
777
+ }
778
+ // Input/Output schemas
779
+ if (agent.inputSchema && agent.inputSchema.length > 0) {
780
+ sections.push("- **Input schema:**");
781
+ for (const field of agent.inputSchema) {
782
+ sections.push(` - ${formatSchemaField(field)}`);
783
+ }
784
+ }
785
+ if (agent.outputSchema && agent.outputSchema.length > 0) {
786
+ sections.push("- **Output schema:**");
787
+ for (const field of agent.outputSchema) {
788
+ sections.push(` - ${formatSchemaField(field)}`);
789
+ }
790
+ }
791
+ // Rate limit
792
+ if (agent.rateLimit) {
793
+ sections.push(`- **Rate limit:** ${agent.rateLimit}`);
794
+ }
795
+ // Produces / Consumes artifacts
796
+ if (agent.produces && agent.produces.length > 0) {
797
+ sections.push(`- **Produces:** ${agent.produces.join(", ")}`);
798
+ }
799
+ if (agent.consumes && agent.consumes.length > 0) {
800
+ sections.push(`- **Consumes:** ${agent.consumes.join(", ")}`);
801
+ }
802
+ // Variants
803
+ if (agent.variants && agent.variants.length > 0) {
804
+ sections.push("- **Prompt variants:**");
805
+ for (const v of agent.variants) {
806
+ let vLine = ` - ${v.id} (weight: ${v.weight})`;
807
+ if (v.temperature != null)
808
+ vLine += `, temp=${v.temperature}`;
809
+ if (v.model)
810
+ vLine += `, model=${v.model}`;
811
+ sections.push(vLine);
812
+ }
813
+ }
814
+ // OpenClaw-specific extensions
815
+ if (agent.extensions?.openclaw) {
816
+ sections.push("- **OpenClaw config:**");
817
+ for (const [k, v] of Object.entries(agent.extensions.openclaw)) {
818
+ sections.push(` - ${k}: ${v}`);
819
+ }
820
+ }
821
+ sections.push("");
822
+ }
823
+ }
824
+ // Human nodes
825
+ const humanNodes = getHumanNodes(ast);
826
+ if (humanNodes.length > 0) {
827
+ sections.push("## Human Pause Points");
828
+ sections.push("");
829
+ for (const human of humanNodes) {
830
+ sections.push(`### ${toTitle(human.id)}`);
831
+ if (human.description) {
832
+ sections.push(`- **Action:** ${human.description}`);
833
+ }
834
+ if (human.timeout) {
835
+ sections.push(`- **Timeout:** ${human.timeout}`);
836
+ }
837
+ if (human.onTimeout) {
838
+ sections.push(`- **On timeout:** ${human.onTimeout}`);
839
+ }
840
+ sections.push("");
841
+ }
842
+ }
843
+ // Group nodes
844
+ const groupNodes = getGroupNodes(ast);
845
+ if (groupNodes.length > 0) {
846
+ sections.push("## Group Chat Coordination");
847
+ sections.push("");
848
+ for (const group of groupNodes) {
849
+ sections.push(`### ${toTitle(group.id)}`);
850
+ sections.push(`- **Members:** ${group.members.join(", ")}`);
851
+ if (group.speakerSelection) {
852
+ sections.push(`- **Speaker selection:** ${group.speakerSelection}`);
853
+ }
854
+ if (group.maxRounds != null) {
855
+ sections.push(`- **Max rounds:** ${group.maxRounds}`);
856
+ }
857
+ if (group.termination) {
858
+ sections.push(`- **Termination:** ${group.termination}`);
859
+ }
860
+ if (group.description) {
861
+ sections.push(`- **Description:** ${group.description}`);
862
+ }
863
+ if (group.timeout) {
864
+ sections.push(`- **Timeout:** ${group.timeout}`);
865
+ }
866
+ sections.push("");
867
+ }
868
+ }
869
+ // Flow
870
+ if (ast.edges.length > 0) {
871
+ sections.push("## Flow");
872
+ sections.push("");
873
+ // Execution order
874
+ sections.push("### Execution Order");
875
+ let idx = 1;
876
+ for (const edge of ast.edges) {
877
+ const arrow = edge.isError ? "-x->" : "->";
878
+ let line = `${idx}. ${edge.from} ${arrow} ${edge.to}`;
879
+ if (edge.condition)
880
+ line += ` when ${edge.condition}`;
881
+ if (edge.maxIterations)
882
+ line += ` (max ${edge.maxIterations} iterations)`;
883
+ if (edge.errorType)
884
+ line += ` [error: ${edge.errorType}]`;
885
+ if (edge.weight != null)
886
+ line += ` [weight: ${edge.weight}]`;
887
+ if (edge.race)
888
+ line += ` [race]`;
889
+ if (edge.tolerance != null)
890
+ line += ` [tolerance: ${edge.tolerance}]`;
891
+ if (edge.wait)
892
+ line += ` [wait: ${edge.wait}]`;
893
+ if (edge.reflection)
894
+ line += ` [reflection]`;
895
+ sections.push(line);
896
+ idx++;
897
+ }
898
+ sections.push("");
899
+ // Conditional routing
900
+ const conditionalEdges = ast.edges.filter((e) => e.condition);
901
+ if (conditionalEdges.length > 0) {
902
+ sections.push("### Conditional Routing");
903
+ for (const edge of conditionalEdges) {
904
+ sections.push(`- If ${edge.condition}: route to ${edge.to}`);
905
+ }
906
+ sections.push("");
907
+ }
908
+ // Fan-out points (nodes with multiple outgoing edges)
909
+ const fromCounts = {};
910
+ for (const edge of ast.edges) {
911
+ if (!fromCounts[edge.from])
912
+ fromCounts[edge.from] = [];
913
+ fromCounts[edge.from].push(edge.to);
914
+ }
915
+ const fanOutNodes = Object.entries(fromCounts).filter(([, targets]) => targets.length > 1);
916
+ if (fanOutNodes.length > 0) {
917
+ sections.push("### Fan-Out Points");
918
+ for (const [from, targets] of fanOutNodes) {
919
+ sections.push(`- After ${from}: spawn [${targets.join(", ")}] in parallel`);
920
+ }
921
+ sections.push("");
922
+ }
923
+ // Bounded loops
924
+ const boundedEdges = ast.edges.filter((e) => e.maxIterations);
925
+ if (boundedEdges.length > 0) {
926
+ sections.push("### Bounded Loops");
927
+ for (const edge of boundedEdges) {
928
+ sections.push(`- ${edge.from} <-> ${edge.to}: max ${edge.maxIterations} iterations (prevents infinite loops)`);
929
+ }
930
+ sections.push("");
931
+ }
932
+ // Error edges
933
+ const errorEdges = ast.edges.filter((e) => e.isError);
934
+ if (errorEdges.length > 0) {
935
+ sections.push("### Error Handling");
936
+ for (const edge of errorEdges) {
937
+ let line = `- On error in ${edge.from}: route to ${edge.to}`;
938
+ if (edge.errorType)
939
+ line += ` (type: ${edge.errorType})`;
940
+ sections.push(line);
941
+ }
942
+ sections.push("");
943
+ }
944
+ // Weighted routing
945
+ const weightedEdges = ast.edges.filter((e) => e.weight != null);
946
+ if (weightedEdges.length > 0) {
947
+ sections.push("### Weighted Routing");
948
+ for (const edge of weightedEdges) {
949
+ sections.push(`- ${edge.from} -> ${edge.to}: weight ${edge.weight}`);
950
+ }
951
+ sections.push("");
952
+ }
953
+ // Reflection loops
954
+ const reflectionEdges = ast.edges.filter((e) => e.reflection);
955
+ if (reflectionEdges.length > 0) {
956
+ sections.push("### Reflection Loops");
957
+ for (const edge of reflectionEdges) {
958
+ sections.push(`- ${edge.from} -> ${edge.to}: evaluator/reflection loop`);
959
+ }
960
+ sections.push("");
961
+ }
962
+ // Race conditions
963
+ const raceEdges = ast.edges.filter((e) => e.race);
964
+ if (raceEdges.length > 0) {
965
+ sections.push("### Race (First-Wins)");
966
+ for (const edge of raceEdges) {
967
+ let line = `- ${edge.from} -> ${edge.to}: race mode`;
968
+ if (edge.tolerance != null)
969
+ line += `, tolerance: ${edge.tolerance}`;
970
+ sections.push(line);
971
+ }
972
+ sections.push("");
973
+ }
974
+ }
975
+ // Gates
976
+ if (gates.length > 0) {
977
+ sections.push("## Gates");
978
+ sections.push("");
979
+ for (const gate of gates) {
980
+ sections.push(`### ${toTitle(gate.id)}`);
981
+ if (gate.after)
982
+ sections.push(`- **After:** ${gate.after}`);
983
+ if (gate.before)
984
+ sections.push(`- **Before:** ${gate.before}`);
985
+ if (gate.run)
986
+ sections.push(`- **Script:** ${gate.run}`);
987
+ if (gate.onFail)
988
+ sections.push(`- **On failure:** ${gate.onFail}`);
989
+ sections.push(`- **Behavior:** ${gate.behavior ?? "blocking"}`);
990
+ if (gate.checks && gate.checks.length > 0) {
991
+ sections.push(`- **Checks:** ${gate.checks.join(", ")}`);
992
+ }
993
+ sections.push(`- **Enforcement:** tools.deny is enforced in openclaw.json; gate scripts are advisory — run manually between agents`);
994
+ sections.push("");
995
+ }
996
+ }
997
+ // Schedule
998
+ if (ast.schedules.length > 0) {
999
+ sections.push("## Schedule");
1000
+ sections.push("");
1001
+ for (const job of ast.schedules) {
1002
+ sections.push(`### ${toTitle(job.id)}`);
1003
+ sections.push(`- **Agent:** ${job.agent}`);
1004
+ if (job.cron)
1005
+ sections.push(`- **Cron:** \`${job.cron}\``);
1006
+ if (job.enabled != null)
1007
+ sections.push(`- **Enabled:** ${job.enabled}`);
1008
+ sections.push("");
1009
+ }
1010
+ }
1011
+ // Hooks
1012
+ if (ast.hooks.length > 0) {
1013
+ sections.push("## Hooks");
1014
+ sections.push("");
1015
+ for (const hook of ast.hooks) {
1016
+ sections.push(`### ${toTitle(hook.name)}`);
1017
+ if (hook.on)
1018
+ sections.push(`- **Event:** ${hook.on}`);
1019
+ if (hook.run)
1020
+ sections.push(`- **Run:** ${hook.run}`);
1021
+ if (hook.type)
1022
+ sections.push(`- **Type:** ${hook.type}`);
1023
+ sections.push("");
1024
+ }
1025
+ }
1026
+ // Topology overview (merged from TEAM.md)
1027
+ sections.push("---");
1028
+ sections.push("");
1029
+ sections.push(`## ${toTitle(ast.topology.name)} -- Topology Overview`);
1030
+ sections.push("");
1031
+ sections.push("### Architecture");
1032
+ sections.push(`- Patterns: ${ast.topology.patterns.join(", ")}`);
1033
+ sections.push(`- Agents: ${agents.length}`);
1034
+ sections.push(`- Gates: ${gates.length}`);
1035
+ sections.push(`- Version: ${ast.topology.version}`);
1036
+ sections.push("");
1037
+ // Flow diagram
1038
+ if (ast.edges.length > 0) {
1039
+ sections.push("### Flow Diagram");
1040
+ sections.push("");
1041
+ sections.push("```mermaid");
1042
+ sections.push("graph TD");
1043
+ for (const edge of ast.edges) {
1044
+ const labelParts = [];
1045
+ if (edge.condition)
1046
+ labelParts.push(edge.condition);
1047
+ if (edge.maxIterations)
1048
+ labelParts.push(`max ${edge.maxIterations}`);
1049
+ if (edge.isError)
1050
+ labelParts.push("error");
1051
+ if (edge.errorType)
1052
+ labelParts.push(`type: ${edge.errorType}`);
1053
+ if (edge.weight != null)
1054
+ labelParts.push(`weight: ${edge.weight}`);
1055
+ if (edge.race)
1056
+ labelParts.push("race");
1057
+ if (edge.tolerance != null)
1058
+ labelParts.push(`tolerance: ${edge.tolerance}`);
1059
+ if (edge.wait)
1060
+ labelParts.push(`wait: ${edge.wait}`);
1061
+ if (edge.reflection)
1062
+ labelParts.push("reflection");
1063
+ const label = labelParts.join(", ");
1064
+ if (label) {
1065
+ if (edge.isError) {
1066
+ sections.push(` ${edge.from} -.->|${label}| ${edge.to}`);
1067
+ }
1068
+ else {
1069
+ sections.push(` ${edge.from} -->|${label}| ${edge.to}`);
1070
+ }
1071
+ }
1072
+ else {
1073
+ sections.push(` ${edge.from} --> ${edge.to}`);
1074
+ }
1075
+ }
1076
+ sections.push("```");
1077
+ sections.push("");
1078
+ }
1079
+ // Roles table
1080
+ if (Object.keys(ast.roles).length > 0) {
1081
+ sections.push("### Roles");
1082
+ sections.push("| Role | Description |");
1083
+ sections.push("|------|-------------|");
1084
+ for (const [role, desc] of Object.entries(ast.roles)) {
1085
+ sections.push(`| ${role} | ${desc} |`);
1086
+ }
1087
+ sections.push("");
1088
+ }
1089
+ // Metering
1090
+ if (ast.metering) {
1091
+ sections.push("### Metering");
1092
+ sections.push(`- Track: ${ast.metering.track.join(", ")}`);
1093
+ sections.push(`- Per: ${ast.metering.per.join(", ")}`);
1094
+ sections.push(`- Output: ${ast.metering.output} (${ast.metering.format})`);
1095
+ sections.push(`- Pricing: ${ast.metering.pricing}`);
1096
+ sections.push("");
1097
+ }
1098
+ return {
1099
+ path: "AGENTS.md",
1100
+ content: sections.join("\n") + "\n",
1101
+ category: "agent",
1102
+ };
1103
+ }
1104
+ /** Generate TOOLS.md — available tools + skill references. */
1105
+ function generateToolsMd(ast) {
1106
+ const agents = getAgentsSorted(ast);
1107
+ const sections = [];
1108
+ sections.push("# Available Tools");
1109
+ sections.push("");
1110
+ // Core tools — unique tools across all agents + settings.allow
1111
+ const allTools = new Set();
1112
+ const settingsAllow = ast.settings?.allow;
1113
+ if (Array.isArray(settingsAllow)) {
1114
+ for (const t of settingsAllow)
1115
+ allTools.add(String(t));
1116
+ }
1117
+ for (const agent of agents) {
1118
+ if (agent.tools) {
1119
+ for (const t of agent.tools)
1120
+ allTools.add(t);
1121
+ }
1122
+ }
1123
+ if (allTools.size > 0) {
1124
+ sections.push("## Core Tools");
1125
+ sections.push("");
1126
+ for (const tool of allTools) {
1127
+ sections.push(`- ${tool}`);
1128
+ }
1129
+ sections.push("");
1130
+ }
1131
+ // Custom tools from toolDefs
1132
+ if (ast.toolDefs.length > 0) {
1133
+ sections.push("## Custom Tools");
1134
+ sections.push("");
1135
+ for (const tool of ast.toolDefs) {
1136
+ sections.push(`### ${tool.id}`);
1137
+ sections.push(tool.description);
1138
+ sections.push(`- Script: ${tool.script}`);
1139
+ if (tool.args && tool.args.length > 0) {
1140
+ sections.push(`- Args: ${tool.args.join(", ")}`);
1141
+ }
1142
+ if (tool.lang) {
1143
+ sections.push(`- Language: ${tool.lang}`);
1144
+ }
1145
+ sections.push("");
1146
+ }
1147
+ }
1148
+ // Skills
1149
+ if (ast.skills.length > 0) {
1150
+ sections.push("## Skills");
1151
+ sections.push("");
1152
+ for (const skill of ast.skills) {
1153
+ sections.push(`### ${skill.id}`);
1154
+ if (skill.description) {
1155
+ sections.push(skill.description);
1156
+ }
1157
+ sections.push("");
1158
+ }
1159
+ }
1160
+ return {
1161
+ path: "TOOLS.md",
1162
+ content: sections.join("\n") + "\n",
1163
+ category: "machine",
1164
+ };
1165
+ }
1166
+ /** Generate MEMORY.md — long-term memory structure. */
1167
+ function generateMemoryMd(ast) {
1168
+ const agents = getAgentsSorted(ast);
1169
+ const memory = ast.memory;
1170
+ const sections = [];
1171
+ sections.push("# Memory Structure");
1172
+ sections.push("");
1173
+ // Domains
1174
+ const domains = memory.domains;
1175
+ if (domains) {
1176
+ sections.push("## Domains");
1177
+ if (domains.path)
1178
+ sections.push(`Path: ${domains.path}`);
1179
+ if (domains.routing)
1180
+ sections.push(`Routing: ${domains.routing}`);
1181
+ sections.push("");
1182
+ }
1183
+ // Workspace
1184
+ const workspace = memory.workspace;
1185
+ if (workspace) {
1186
+ sections.push("## Workspace");
1187
+ if (workspace.path)
1188
+ sections.push(`Path: ${workspace.path}`);
1189
+ if (workspace.structure && Array.isArray(workspace.structure)) {
1190
+ sections.push(`Structure: ${workspace.structure.join(", ")}`);
1191
+ }
1192
+ sections.push("");
1193
+ }
1194
+ // Metrics
1195
+ const metrics = memory.metrics;
1196
+ if (metrics) {
1197
+ sections.push("## Metrics");
1198
+ if (metrics.path)
1199
+ sections.push(`Path: ${metrics.path}`);
1200
+ if (metrics.mode)
1201
+ sections.push(`Mode: ${metrics.mode}`);
1202
+ sections.push("");
1203
+ }
1204
+ // Agent access rules
1205
+ const agentsWithAccess = agents.filter((a) => (a.reads && a.reads.length > 0) || (a.writes && a.writes.length > 0));
1206
+ if (agentsWithAccess.length > 0) {
1207
+ sections.push("## Agent Access Rules");
1208
+ sections.push("");
1209
+ for (const agent of agentsWithAccess) {
1210
+ sections.push(`### ${agent.id}`);
1211
+ if (agent.reads && agent.reads.length > 0) {
1212
+ sections.push(`- Reads: ${agent.reads.join(", ")}`);
1213
+ }
1214
+ if (agent.writes && agent.writes.length > 0) {
1215
+ sections.push(`- Writes: ${agent.writes.join(", ")}`);
1216
+ }
1217
+ sections.push("");
1218
+ }
1219
+ }
1220
+ return {
1221
+ path: "MEMORY.md",
1222
+ content: sections.join("\n") + "\n",
1223
+ category: "machine",
1224
+ };
1225
+ }
1226
+ /** Generate BOOTSTRAP.md — init instructions from hooks + gates. */
1227
+ function generateBootstrapMd(ast) {
1228
+ const agents = getAgentsSorted(ast);
1229
+ const gates = getGates(ast);
1230
+ const sections = [];
1231
+ sections.push("# Bootstrap Sequence");
1232
+ sections.push("");
1233
+ // Initialization hooks
1234
+ const initHooks = ast.hooks.filter((h) => h.on === "SessionStart" || h.on === "InstructionsLoaded");
1235
+ if (initHooks.length > 0) {
1236
+ sections.push("## Initialization");
1237
+ let idx = 1;
1238
+ for (const hook of initHooks) {
1239
+ sections.push(`${idx}. Run: ${hook.run}`);
1240
+ idx++;
1241
+ }
1242
+ sections.push("");
1243
+ }
1244
+ // Pre-flight gates (gates before the first flow node)
1245
+ const firstEdge = ast.edges.length > 0 ? ast.edges[0] : null;
1246
+ const preFlightGates = gates.filter((g) => firstEdge && g.before === firstEdge.from);
1247
+ if (preFlightGates.length > 0) {
1248
+ sections.push("## Pre-Flight Gates");
1249
+ sections.push("");
1250
+ sections.push("> **Enforcement note:** Gate scripts are advisory — they must be run manually");
1251
+ sections.push("> between agents. Tool deny lists (`tools.deny` in openclaw.json) ARE enforced");
1252
+ sections.push("> by the runtime. Gate scripts provide quality checks but not hard enforcement.");
1253
+ sections.push("");
1254
+ let idx = 1;
1255
+ for (const gate of preFlightGates) {
1256
+ sections.push(`${idx}. Gate: ${gate.id} -- ${gate.run ?? "manual check"}`);
1257
+ sections.push(` On failure: ${gate.onFail ?? "halt"}`);
1258
+ idx++;
1259
+ }
1260
+ sections.push("");
1261
+ }
1262
+ // Agent initialization order
1263
+ if (agents.length > 0) {
1264
+ sections.push("## Agent Initialization Order");
1265
+ sections.push("");
1266
+ for (const agent of agents) {
1267
+ const phase = agent.phase != null ? agent.phase : 0;
1268
+ sections.push(`Phase ${phase}: ${agent.id} (${mapModel(agent.model ?? "sonnet")})`);
1269
+ }
1270
+ sections.push("");
1271
+ }
1272
+ return {
1273
+ path: "BOOTSTRAP.md",
1274
+ content: sections.join("\n") + "\n",
1275
+ category: "machine",
1276
+ };
1277
+ }
1278
+ /** Generate USER.md — user profile for agent personalization. */
1279
+ function generateUserMd() {
1280
+ const sections = [];
1281
+ sections.push("# User Profile");
1282
+ sections.push("");
1283
+ sections.push("Describe who you are so the agent can personalize responses.");
1284
+ sections.push("");
1285
+ sections.push("<!-- Edit this file with your preferences, name, communication style, etc. -->");
1286
+ sections.push("");
1287
+ return {
1288
+ path: "USER.md",
1289
+ content: sections.join("\n"),
1290
+ category: "agent",
1291
+ };
1292
+ }
1293
+ /** Generate IDENTITY.md — agent identity from topology meta. */
1294
+ function generateIdentityMd(ast) {
1295
+ const sections = [];
1296
+ sections.push("# Identity");
1297
+ sections.push("");
1298
+ sections.push(`- **Name:** ${toTitle(ast.topology.name)}`);
1299
+ sections.push(`- **Emoji:** \uD83E\uDD9E`);
1300
+ const vibe = ast.topology.description ?? "Coordinate agent topology.";
1301
+ sections.push(`- **Vibe:** ${vibe}`);
1302
+ sections.push("");
1303
+ return {
1304
+ path: "IDENTITY.md",
1305
+ content: sections.join("\n"),
1306
+ category: "agent",
1307
+ };
1308
+ }
1309
+ /** Generate workspace-protocol.md — access rules from agent reads/writes. */
1310
+ function generateWorkspaceProtocol(ast) {
1311
+ const agents = getAgentsSorted(ast);
1312
+ const agentsWithAccess = agents.filter((a) => (a.reads && a.reads.length > 0) || (a.writes && a.writes.length > 0));
1313
+ if (agentsWithAccess.length === 0)
1314
+ return null;
1315
+ const sections = [];
1316
+ sections.push("# Workspace Protocol");
1317
+ sections.push("");
1318
+ // Directory structure from workspace
1319
+ const workspace = ast.memory.workspace;
1320
+ if (workspace?.structure && Array.isArray(workspace.structure)) {
1321
+ sections.push("## Directory Structure");
1322
+ for (const item of workspace.structure) {
1323
+ sections.push(`- ${item}/`);
1324
+ }
1325
+ sections.push("");
1326
+ }
1327
+ // Read/Write rules
1328
+ sections.push("## Access Rules");
1329
+ sections.push("");
1330
+ for (const agent of agentsWithAccess) {
1331
+ sections.push(`### ${toTitle(agent.id)}`);
1332
+ if (agent.reads && agent.reads.length > 0) {
1333
+ sections.push(`- **Reads:** ${agent.reads.join(", ")}`);
1334
+ }
1335
+ if (agent.writes && agent.writes.length > 0) {
1336
+ sections.push(`- **Writes:** ${agent.writes.join(", ")}`);
1337
+ }
1338
+ sections.push("");
1339
+ }
1340
+ return {
1341
+ path: "workspace-protocol.md",
1342
+ content: sections.join("\n") + "\n",
1343
+ category: "machine",
1344
+ };
1345
+ }
1346
+ /** Generate skill files from ast.skills. */
1347
+ function generateSkillFiles(ast) {
1348
+ const files = [];
1349
+ for (const skill of ast.skills) {
1350
+ const sections = [];
1351
+ // OpenClaw requires YAML frontmatter with name and description
1352
+ sections.push("---");
1353
+ sections.push(`name: ${skill.id}`);
1354
+ if (skill.description) {
1355
+ sections.push(`description: ${JSON.stringify(skill.description)}`);
1356
+ }
1357
+ if (skill.userInvocable != null) {
1358
+ sections.push(`user-invocable: ${skill.userInvocable}`);
1359
+ }
1360
+ sections.push("---");
1361
+ sections.push("");
1362
+ sections.push(`# ${toTitle(skill.id)} Skill`);
1363
+ sections.push("");
1364
+ if (skill.description) {
1365
+ sections.push(skill.description);
1366
+ sections.push("");
1367
+ }
1368
+ if (skill.scripts && skill.scripts.length > 0) {
1369
+ sections.push("## Scripts");
1370
+ for (const s of skill.scripts) {
1371
+ sections.push(`- ${s}`);
1372
+ }
1373
+ sections.push("");
1374
+ }
1375
+ if (skill.domains && skill.domains.length > 0) {
1376
+ sections.push("## Domains");
1377
+ for (const d of skill.domains) {
1378
+ sections.push(`- ${d}`);
1379
+ }
1380
+ sections.push("");
1381
+ }
1382
+ if (skill.references && skill.references.length > 0) {
1383
+ sections.push("## References");
1384
+ for (const r of skill.references) {
1385
+ sections.push(`- ${r}`);
1386
+ }
1387
+ sections.push("");
1388
+ }
1389
+ files.push({
1390
+ path: `skills/${skill.id}/SKILL.md`,
1391
+ content: sections.join("\n") + "\n",
1392
+ category: "agent",
1393
+ });
1394
+ }
1395
+ return files;
1396
+ }
1397
+ /** Generate tool-as-skill wrappers from ast.toolDefs. */
1398
+ function generateToolSkills(ast) {
1399
+ const files = [];
1400
+ for (const tool of ast.toolDefs) {
1401
+ // SKILL.md wrapper — OpenClaw requires YAML frontmatter
1402
+ const sections = [];
1403
+ sections.push("---");
1404
+ sections.push(`name: ${tool.id}`);
1405
+ sections.push(`description: ${JSON.stringify(tool.description)}`);
1406
+ sections.push("---");
1407
+ sections.push("");
1408
+ sections.push(`# ${toTitle(tool.id)} Tool`);
1409
+ sections.push("");
1410
+ sections.push(tool.description);
1411
+ sections.push("");
1412
+ sections.push(`- Script: \`${tool.script}\``);
1413
+ if (tool.args && tool.args.length > 0) {
1414
+ sections.push(`- Args: ${tool.args.join(", ")}`);
1415
+ }
1416
+ if (tool.lang) {
1417
+ sections.push(`- Language: ${tool.lang}`);
1418
+ }
1419
+ sections.push("");
1420
+ files.push({
1421
+ path: `skills/${tool.id}/SKILL.md`,
1422
+ content: sections.join("\n") + "\n",
1423
+ category: "agent",
1424
+ });
1425
+ // index.ts stub
1426
+ const ext = tool.lang === "python" ? "py" : tool.lang === "node" ? "js" : "ts";
1427
+ const stubLines = [
1428
+ "// Auto-generated tool stub — edit as needed.",
1429
+ `// Tool: ${tool.id}`,
1430
+ `// ${tool.description}`,
1431
+ "",
1432
+ ];
1433
+ if (tool.args && tool.args.length > 0) {
1434
+ stubLines.push(`// Args: ${tool.args.join(", ")}`);
1435
+ stubLines.push("");
1436
+ }
1437
+ stubLines.push('console.log("TODO: implement tool");');
1438
+ stubLines.push("");
1439
+ files.push({
1440
+ path: `skills/${tool.id}/index.${ext}`,
1441
+ content: stubLines.join("\n"),
1442
+ category: "script",
1443
+ });
1444
+ }
1445
+ return files;
1446
+ }
1447
+ /** Generate gate script stubs. */
1448
+ function generateGateScripts(ast) {
1449
+ const files = [];
1450
+ const gates = getGates(ast);
1451
+ for (const gate of gates) {
1452
+ if (gate.run) {
1453
+ const scriptName = gate.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1454
+ files.push({
1455
+ path: `scripts/${scriptName}`,
1456
+ content: shellStub(`Gate: ${gate.id} -- ${gate.run}`),
1457
+ category: "script",
1458
+ });
1459
+ }
1460
+ }
1461
+ return files;
1462
+ }
1463
+ /** Generate hook script stubs. */
1464
+ function generateHookScripts(ast) {
1465
+ const files = [];
1466
+ for (const hook of ast.hooks) {
1467
+ if (!hook.run)
1468
+ continue;
1469
+ const scriptName = hook.run.replace(/^.*\//, "").replace(/\s.*$/, "");
1470
+ files.push({
1471
+ path: `scripts/${scriptName}`,
1472
+ content: [
1473
+ "#!/usr/bin/env bash",
1474
+ `# Hook: ${hook.name} -- fires on ${hook.on}`,
1475
+ "# Auto-generated by agentopology scaffold — edit as needed.",
1476
+ "set -euo pipefail",
1477
+ "",
1478
+ `echo "TODO: implement ${hook.name} hook"`,
1479
+ "",
1480
+ ].join("\n"),
1481
+ category: "script",
1482
+ });
1483
+ }
1484
+ return files;
1485
+ }
1486
+ /** Generate memory directories (workspace structure + domains). */
1487
+ function generateMemoryDirs(ast) {
1488
+ const files = [];
1489
+ // Workspace dirs
1490
+ const workspace = ast.memory.workspace;
1491
+ if (workspace) {
1492
+ files.push(gitkeep("workspace"));
1493
+ if (workspace.structure && Array.isArray(workspace.structure)) {
1494
+ for (const dir of workspace.structure) {
1495
+ files.push(gitkeep(`workspace/${dir}`));
1496
+ }
1497
+ }
1498
+ }
1499
+ // Domains dir
1500
+ files.push(gitkeep("domains"));
1501
+ return files;
1502
+ }
1503
+ /** Generate metering script + metrics file. */
1504
+ function generateMetering(ast) {
1505
+ if (!ast.metering)
1506
+ return [];
1507
+ const m = ast.metering;
1508
+ const files = [];
1509
+ // collect-metrics.sh
1510
+ const lines = [
1511
+ "#!/usr/bin/env bash",
1512
+ "# Metering / cost tracking script",
1513
+ "# Auto-generated by agentopology scaffold — edit as needed.",
1514
+ "set -euo pipefail",
1515
+ "",
1516
+ `# Track: ${m.track.join(", ")}`,
1517
+ `# Per: ${m.per.join(", ")}`,
1518
+ `# Output: ${m.output} (${m.format})`,
1519
+ `# Pricing: ${m.pricing}`,
1520
+ "",
1521
+ 'echo "TODO: implement metrics collection"',
1522
+ "",
1523
+ ];
1524
+ files.push({
1525
+ path: "scripts/collect-metrics.sh",
1526
+ content: lines.join("\n"),
1527
+ category: "script",
1528
+ });
1529
+ // Empty metrics file
1530
+ files.push({
1531
+ path: "metrics.jsonl",
1532
+ content: "",
1533
+ category: "machine",
1534
+ });
1535
+ return files;
1536
+ }
1537
+ // ---------------------------------------------------------------------------
1538
+ // Binding export
1539
+ // ---------------------------------------------------------------------------
1540
+ /** OpenClaw binding — multi-channel AI agent framework. */
1541
+ export const openClawBinding = {
1542
+ name: "openclaw",
1543
+ description: "OpenClaw workspace -- multi-channel AI agent framework with gateway, channels, and markdown-based agent identity",
1544
+ scaffold(ast) {
1545
+ const files = [];
1546
+ // 1. Core workspace files
1547
+ files.push(generateOpenClawJson(ast));
1548
+ files.push(generateSoulMd(ast));
1549
+ files.push(generateAgentsMd(ast));
1550
+ files.push(generateToolsMd(ast));
1551
+ files.push(generateMemoryMd(ast));
1552
+ files.push(generateBootstrapMd(ast));
1553
+ files.push(generateUserMd());
1554
+ files.push(generateIdentityMd(ast));
1555
+ // 1b. Cron jobs file (separate from openclaw.json)
1556
+ const cronJobs = generateCronJobsJson(ast);
1557
+ if (cronJobs)
1558
+ files.push(cronJobs);
1559
+ // 2. Skills (from ast.skills + ast.toolDefs)
1560
+ files.push(...generateSkillFiles(ast));
1561
+ files.push(...generateToolSkills(ast));
1562
+ // 3. Scripts (gates + hooks + metering)
1563
+ files.push(...generateGateScripts(ast));
1564
+ files.push(...generateHookScripts(ast));
1565
+ files.push(...generateMetering(ast));
1566
+ // 4. Memory directories
1567
+ files.push(...generateMemoryDirs(ast));
1568
+ // 5. Memory and skills directory markers
1569
+ files.push(gitkeep("memory"));
1570
+ files.push(gitkeep("skills"));
1571
+ // 6. Workspace protocol
1572
+ const protocol = generateWorkspaceProtocol(ast);
1573
+ if (protocol)
1574
+ files.push(protocol);
1575
+ return deduplicateFiles(files);
1576
+ },
1577
+ };
1578
+ //# sourceMappingURL=openclaw.js.map