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