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.
- package/LICENSE +191 -0
- package/README.md +450 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/analyzer/__tests__/analyzer.test.js +258 -0
- package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
- package/dist/analyzer/index.d.ts +44 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +308 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
- package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
- package/dist/bindings/__tests__/bindings.test.js +2089 -0
- package/dist/bindings/__tests__/bindings.test.js.map +1 -0
- package/dist/bindings/claude-code.d.ts +12 -0
- package/dist/bindings/claude-code.d.ts.map +1 -0
- package/dist/bindings/claude-code.js +2063 -0
- package/dist/bindings/claude-code.js.map +1 -0
- package/dist/bindings/codex.d.ts +18 -0
- package/dist/bindings/codex.d.ts.map +1 -0
- package/dist/bindings/codex.js +1633 -0
- package/dist/bindings/codex.js.map +1 -0
- package/dist/bindings/copilot-cli.d.ts +20 -0
- package/dist/bindings/copilot-cli.d.ts.map +1 -0
- package/dist/bindings/copilot-cli.js +1515 -0
- package/dist/bindings/copilot-cli.js.map +1 -0
- package/dist/bindings/cursor.d.ts +17 -0
- package/dist/bindings/cursor.d.ts.map +1 -0
- package/dist/bindings/cursor.js +1715 -0
- package/dist/bindings/cursor.js.map +1 -0
- package/dist/bindings/gemini-cli.d.ts +18 -0
- package/dist/bindings/gemini-cli.d.ts.map +1 -0
- package/dist/bindings/gemini-cli.js +1327 -0
- package/dist/bindings/gemini-cli.js.map +1 -0
- package/dist/bindings/index.d.ts +20 -0
- package/dist/bindings/index.d.ts.map +1 -0
- package/dist/bindings/index.js +33 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/bindings/kiro.d.ts +17 -0
- package/dist/bindings/kiro.d.ts.map +1 -0
- package/dist/bindings/kiro.js +1624 -0
- package/dist/bindings/kiro.js.map +1 -0
- package/dist/bindings/openclaw.d.ts +18 -0
- package/dist/bindings/openclaw.d.ts.map +1 -0
- package/dist/bindings/openclaw.js +1578 -0
- package/dist/bindings/openclaw.js.map +1 -0
- package/dist/bindings/types.d.ts +33 -0
- package/dist/bindings/types.d.ts.map +1 -0
- package/dist/bindings/types.js +23 -0
- package/dist/bindings/types.js.map +1 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +679 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/docs/__tests__/docs.test.d.ts +2 -0
- package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
- package/dist/docs/__tests__/docs.test.js +438 -0
- package/dist/docs/__tests__/docs.test.js.map +1 -0
- package/dist/docs/content.d.ts +16 -0
- package/dist/docs/content.d.ts.map +1 -0
- package/dist/docs/content.js +3273 -0
- package/dist/docs/content.js.map +1 -0
- package/dist/docs/index.d.ts +17 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +117 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
- package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
- package/dist/exporters/__tests__/exporters.test.js +731 -0
- package/dist/exporters/__tests__/exporters.test.js.map +1 -0
- package/dist/exporters/index.d.ts +13 -0
- package/dist/exporters/index.d.ts.map +1 -0
- package/dist/exporters/index.js +18 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters/json.d.ts +8 -0
- package/dist/exporters/json.d.ts.map +1 -0
- package/dist/exporters/json.js +25 -0
- package/dist/exporters/json.js.map +1 -0
- package/dist/exporters/markdown.d.ts +9 -0
- package/dist/exporters/markdown.d.ts.map +1 -0
- package/dist/exporters/markdown.js +686 -0
- package/dist/exporters/markdown.js.map +1 -0
- package/dist/exporters/mermaid.d.ts +11 -0
- package/dist/exporters/mermaid.d.ts.map +1 -0
- package/dist/exporters/mermaid.js +161 -0
- package/dist/exporters/mermaid.js.map +1 -0
- package/dist/exporters/types.d.ts +22 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +10 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/import/__tests__/import.test.d.ts +2 -0
- package/dist/import/__tests__/import.test.d.ts.map +1 -0
- package/dist/import/__tests__/import.test.js +781 -0
- package/dist/import/__tests__/import.test.js.map +1 -0
- package/dist/import/claude-code.d.ts +38 -0
- package/dist/import/claude-code.d.ts.map +1 -0
- package/dist/import/claude-code.js +897 -0
- package/dist/import/claude-code.js.map +1 -0
- package/dist/import/index.d.ts +18 -0
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +28 -0
- package/dist/import/index.js.map +1 -0
- package/dist/import/serializer.d.ts +11 -0
- package/dist/import/serializer.d.ts.map +1 -0
- package/dist/import/serializer.js +960 -0
- package/dist/import/serializer.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/__tests__/parser.test.d.ts +2 -0
- package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
- package/dist/parser/__tests__/parser.test.js +6514 -0
- package/dist/parser/__tests__/parser.test.js.map +1 -0
- package/dist/parser/ast.d.ts +708 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +12 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/index.d.ts +253 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +1985 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/lexer.d.ts +100 -0
- package/dist/parser/lexer.d.ts.map +1 -0
- package/dist/parser/lexer.js +304 -0
- package/dist/parser/lexer.js.map +1 -0
- package/dist/parser/validator.d.ts +42 -0
- package/dist/parser/validator.d.ts.map +1 -0
- package/dist/parser/validator.js +2568 -0
- package/dist/parser/validator.js.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.js +295 -0
- package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
- package/dist/scaffold/incremental.d.ts +43 -0
- package/dist/scaffold/incremental.d.ts.map +1 -0
- package/dist/scaffold/incremental.js +185 -0
- package/dist/scaffold/incremental.js.map +1 -0
- package/dist/scaffold/index.d.ts +13 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +13 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/manifest.d.ts +30 -0
- package/dist/scaffold/manifest.d.ts.map +1 -0
- package/dist/scaffold/manifest.js +71 -0
- package/dist/scaffold/manifest.js.map +1 -0
- package/dist/scaffold/merge.d.ts +52 -0
- package/dist/scaffold/merge.d.ts.map +1 -0
- package/dist/scaffold/merge.js +134 -0
- package/dist/scaffold/merge.js.map +1 -0
- package/dist/scaffold/types.d.ts +51 -0
- package/dist/scaffold/types.d.ts.map +1 -0
- package/dist/scaffold/types.js +10 -0
- package/dist/scaffold/types.js.map +1 -0
- package/dist/sync/index.d.ts +34 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +392 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/visualizer/index.d.ts +17 -0
- package/dist/visualizer/index.d.ts.map +1 -0
- package/dist/visualizer/index.js +1976 -0
- package/dist/visualizer/index.js.map +1 -0
- package/examples/code-review.at +167 -0
- package/examples/data-processing.at +296 -0
- package/examples/openclaw-assistant.at +142 -0
- package/examples/scheduled-monitor.at +193 -0
- package/examples/simple-pipeline.at +163 -0
- package/package.json +77 -0
- package/skill/SKILL.md +403 -0
- package/skill/scripts/emit.ts +853 -0
- package/skill/scripts/visualize.ts +89 -0
- package/spec/grammar.md +1466 -0
- package/spec/reserved-keywords.md +147 -0
- 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
|