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.
- package/README.md +17 -19
- package/dist/__tests__/e2e/action-guard.test.d.ts +13 -0
- package/dist/__tests__/e2e/action-guard.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/action-guard.test.js +299 -0
- package/dist/__tests__/e2e/action-guard.test.js.map +1 -0
- package/dist/__tests__/e2e/compile.test.js +1 -1
- package/dist/__tests__/e2e/compile.test.js.map +1 -1
- package/dist/__tests__/e2e/conformance.test.d.ts +13 -0
- package/dist/__tests__/e2e/conformance.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/conformance.test.js +109 -0
- package/dist/__tests__/e2e/conformance.test.js.map +1 -0
- package/dist/__tests__/e2e/contract-drift.test.d.ts +11 -0
- package/dist/__tests__/e2e/contract-drift.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/contract-drift.test.js +142 -0
- package/dist/__tests__/e2e/contract-drift.test.js.map +1 -0
- package/dist/__tests__/e2e/fail-closed.test.d.ts +11 -0
- package/dist/__tests__/e2e/fail-closed.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/fail-closed.test.js +111 -0
- package/dist/__tests__/e2e/fail-closed.test.js.map +1 -0
- package/dist/__tests__/e2e/guard-check.test.d.ts +9 -0
- package/dist/__tests__/e2e/guard-check.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/guard-check.test.js +316 -0
- package/dist/__tests__/e2e/guard-check.test.js.map +1 -0
- package/dist/__tests__/e2e/guard-target.test.d.ts +11 -0
- package/dist/__tests__/e2e/guard-target.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/guard-target.test.js +198 -0
- package/dist/__tests__/e2e/guard-target.test.js.map +1 -0
- package/dist/__tests__/e2e/inventory.test.d.ts +18 -0
- package/dist/__tests__/e2e/inventory.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/inventory.test.js +187 -0
- package/dist/__tests__/e2e/inventory.test.js.map +1 -0
- package/dist/__tests__/e2e/matcher.test.d.ts +14 -0
- package/dist/__tests__/e2e/matcher.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/matcher.test.js +145 -0
- package/dist/__tests__/e2e/matcher.test.js.map +1 -0
- package/dist/__tests__/e2e/step-check.test.d.ts +9 -0
- package/dist/__tests__/e2e/step-check.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/step-check.test.js +158 -0
- package/dist/__tests__/e2e/step-check.test.js.map +1 -0
- package/dist/__tests__/e2e/strength.test.d.ts +17 -0
- package/dist/__tests__/e2e/strength.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/strength.test.js +130 -0
- package/dist/__tests__/e2e/strength.test.js.map +1 -0
- package/dist/__tests__/e2e/verified-on.test.d.ts +2 -0
- package/dist/__tests__/e2e/verified-on.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/verified-on.test.js +57 -0
- package/dist/__tests__/e2e/verified-on.test.js.map +1 -0
- package/dist/__tests__/open-findings.test.js +1 -0
- package/dist/__tests__/open-findings.test.js.map +1 -1
- package/dist/bindings/__tests__/bindings.test.js +6 -686
- package/dist/bindings/__tests__/bindings.test.js.map +1 -1
- package/dist/bindings/__tests__/defaults-agreement.test.js +0 -12
- package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -1
- package/dist/bindings/__tests__/hook-command.test.js +2 -2
- package/dist/bindings/__tests__/hook-command.test.js.map +1 -1
- package/dist/bindings/__tests__/mod-plugin.test.js +2 -2
- package/dist/bindings/__tests__/mod-plugin.test.js.map +1 -1
- package/dist/bindings/claude-code.d.ts +45 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +191 -19
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/index.d.ts +5 -5
- package/dist/bindings/index.d.ts.map +1 -1
- package/dist/bindings/index.js +9 -16
- package/dist/bindings/index.js.map +1 -1
- package/dist/bindings/lib/stub.d.ts +15 -1
- package/dist/bindings/lib/stub.d.ts.map +1 -1
- package/dist/bindings/lib/stub.js +27 -2
- package/dist/bindings/lib/stub.js.map +1 -1
- package/dist/bindings/types.d.ts +6 -1
- package/dist/bindings/types.d.ts.map +1 -1
- package/dist/cli/__tests__/cli.test.js +25 -0
- package/dist/cli/__tests__/cli.test.js.map +1 -1
- package/dist/cli/index.js +222 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/conformance/claude-code.d.ts +99 -0
- package/dist/conformance/claude-code.d.ts.map +1 -0
- package/dist/conformance/claude-code.js +242 -0
- package/dist/conformance/claude-code.js.map +1 -0
- package/dist/conformance/extract.d.ts +70 -0
- package/dist/conformance/extract.d.ts.map +1 -0
- package/dist/conformance/extract.js +185 -0
- package/dist/conformance/extract.js.map +1 -0
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +57 -32
- package/dist/docs/content.js.map +1 -1
- package/dist/guard/check.d.ts +53 -0
- package/dist/guard/check.d.ts.map +1 -0
- package/dist/guard/check.js +366 -0
- package/dist/guard/check.js.map +1 -0
- package/dist/guard/target.d.ts +45 -0
- package/dist/guard/target.d.ts.map +1 -0
- package/dist/guard/target.js +168 -0
- package/dist/guard/target.js.map +1 -0
- package/dist/import/__tests__/import.test.js +0 -554
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/import/index.d.ts +0 -1
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -7
- package/dist/import/index.js.map +1 -1
- package/dist/import/serializer.d.ts.map +1 -1
- package/dist/import/serializer.js +41 -0
- package/dist/import/serializer.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/inventory/index.d.ts +65 -0
- package/dist/inventory/index.d.ts.map +1 -0
- package/dist/inventory/index.js +272 -0
- package/dist/inventory/index.js.map +1 -0
- package/dist/parser/ast.d.ts +46 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/index.d.ts +6 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +66 -4
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/known-fields.d.ts +3 -0
- package/dist/parser/known-fields.d.ts.map +1 -1
- package/dist/parser/known-fields.js +5 -3
- package/dist/parser/known-fields.js.map +1 -1
- package/dist/parser/ledger.d.ts +36 -0
- package/dist/parser/ledger.d.ts.map +1 -0
- package/dist/parser/ledger.js +72 -0
- package/dist/parser/ledger.js.map +1 -0
- package/dist/parser/run.d.ts +6 -0
- package/dist/parser/run.d.ts.map +1 -1
- package/dist/parser/run.js +10 -0
- package/dist/parser/run.js.map +1 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +255 -1
- package/dist/parser/validator.js.map +1 -1
- package/dist/scaffold/manifest.d.ts.map +1 -1
- package/dist/scaffold/manifest.js +0 -8
- package/dist/scaffold/manifest.js.map +1 -1
- package/dist/scaffold/merge.d.ts.map +1 -1
- package/dist/scaffold/merge.js +1 -3
- package/dist/scaffold/merge.js.map +1 -1
- package/dist/step/check.d.ts +56 -0
- package/dist/step/check.d.ts.map +1 -0
- package/dist/step/check.js +125 -0
- package/dist/step/check.js.map +1 -0
- package/dist/sync/index.d.ts +0 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +0 -109
- package/dist/sync/index.js.map +1 -1
- package/package.json +3 -1
- package/spec/claude-code/README.md +19 -0
- package/spec/claude-code/hooks-contract.2.1.295.json +143 -0
- package/spec/claude-code/hooks-contract.json +142 -0
- package/spec/grammar.md +45 -7
- package/spec/testing.md +16 -0
- package/spec/validation.md +11 -2
- package/dist/__tests__/live/zz-debug.test.d.ts +0 -2
- package/dist/__tests__/live/zz-debug.test.d.ts.map +0 -1
- package/dist/__tests__/live/zz-debug.test.js +0 -4
- package/dist/__tests__/live/zz-debug.test.js.map +0 -1
- package/dist/bindings/copilot-cli.d.ts +0 -20
- package/dist/bindings/copilot-cli.d.ts.map +0 -1
- package/dist/bindings/copilot-cli.js +0 -1516
- package/dist/bindings/copilot-cli.js.map +0 -1
- package/dist/bindings/gemini-cli.d.ts +0 -18
- package/dist/bindings/gemini-cli.d.ts.map +0 -1
- package/dist/bindings/gemini-cli.js +0 -1318
- package/dist/bindings/gemini-cli.js.map +0 -1
- package/dist/bindings/kiro.d.ts +0 -17
- package/dist/bindings/kiro.d.ts.map +0 -1
- package/dist/bindings/kiro.js +0 -1615
- package/dist/bindings/kiro.js.map +0 -1
- package/dist/bindings/openclaw.d.ts +0 -18
- package/dist/bindings/openclaw.d.ts.map +0 -1
- package/dist/bindings/openclaw.js +0 -1947
- package/dist/bindings/openclaw.js.map +0 -1
- package/dist/import/openclaw.d.ts +0 -70
- package/dist/import/openclaw.d.ts.map +0 -1
- package/dist/import/openclaw.js +0 -1060
- 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
|