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