agentopology 0.1.1 → 0.2.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 +1 -1
- package/dist/analyzer/index.d.ts +1 -0
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +35 -0
- package/dist/analyzer/index.js.map +1 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +283 -2
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/openclaw.d.ts.map +1 -1
- package/dist/bindings/openclaw.js +367 -5
- package/dist/bindings/openclaw.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/docs/__tests__/docs.test.js +2 -1
- package/dist/docs/__tests__/docs.test.js.map +1 -1
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +730 -4
- package/dist/docs/content.js.map +1 -1
- package/dist/exporters/markdown.d.ts.map +1 -1
- package/dist/exporters/markdown.js +165 -0
- package/dist/exporters/markdown.js.map +1 -1
- package/dist/exporters/mermaid.d.ts.map +1 -1
- package/dist/exporters/mermaid.js +30 -0
- package/dist/exporters/mermaid.js.map +1 -1
- package/dist/import/__tests__/import.test.js +554 -0
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/import/claude-code.d.ts.map +1 -1
- package/dist/import/claude-code.js +2 -0
- package/dist/import/claude-code.js.map +1 -1
- package/dist/import/index.d.ts +1 -0
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +7 -1
- package/dist/import/index.js.map +1 -1
- package/dist/import/openclaw.d.ts +70 -0
- package/dist/import/openclaw.d.ts.map +1 -0
- package/dist/import/openclaw.js +1060 -0
- package/dist/import/openclaw.js.map +1 -0
- package/dist/import/serializer.d.ts.map +1 -1
- package/dist/import/serializer.js +171 -4
- package/dist/import/serializer.js.map +1 -1
- package/dist/parser/__tests__/parser.test.js +463 -0
- package/dist/parser/__tests__/parser.test.js.map +1 -1
- package/dist/parser/ast.d.ts +122 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/index.d.ts +14 -2
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +226 -5
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/validator.d.ts +1 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +214 -1
- package/dist/parser/validator.js.map +1 -1
- package/dist/visualizer/index.d.ts.map +1 -1
- package/dist/visualizer/index.js +104 -16
- package/dist/visualizer/index.js.map +1 -1
- package/examples/memory-system.at +162 -0
- package/examples/script-agent-pipeline.at +65 -0
- package/package.json +1 -1
- package/spec/grammar.md +345 -10
- package/spec/reserved-keywords.md +25 -1
- package/spec/validation.md +171 -2
- package/examples/simple-pipeline-topology.html +0 -5700
|
@@ -778,4 +778,558 @@ topology complex : [pipeline, fan-out] {
|
|
|
778
778
|
expect(lead.outputs.confidence).toContain("high");
|
|
779
779
|
});
|
|
780
780
|
});
|
|
781
|
+
// ===========================================================================
|
|
782
|
+
// OpenClaw importer tests
|
|
783
|
+
// ===========================================================================
|
|
784
|
+
import { parseOpenClawJson, parseSoulMd, parseAgentsMd, parseSkillFile, parseMemoryMd, parseCronJobsJson, importOpenClaw, } from "../openclaw.js";
|
|
785
|
+
// ---------------------------------------------------------------------------
|
|
786
|
+
// OpenClaw test data
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
const OPENCLAW_JSON = `{
|
|
789
|
+
"agents": {
|
|
790
|
+
"defaults": {
|
|
791
|
+
"workspace": "~/.openclaw/workspace",
|
|
792
|
+
"model": { "primary": "openrouter/google/gemini-2.5-flash" }
|
|
793
|
+
},
|
|
794
|
+
"list": [
|
|
795
|
+
{
|
|
796
|
+
"id": "commander",
|
|
797
|
+
"name": "Commander",
|
|
798
|
+
"model": { "primary": "openrouter/google/gemini-2.5-flash" },
|
|
799
|
+
"workspace": "~/.openclaw/workspace-commander",
|
|
800
|
+
"agentDir": "~/.openclaw/config/agents/commander/agent",
|
|
801
|
+
"subagents": { "allowAgents": ["researcher", "worker"] }
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
"id": "researcher",
|
|
805
|
+
"name": "Researcher",
|
|
806
|
+
"model": {
|
|
807
|
+
"primary": "openrouter/google/gemini-2.5-flash",
|
|
808
|
+
"fallbacks": ["openrouter/google/gemini-2.5-flash-lite"]
|
|
809
|
+
},
|
|
810
|
+
"workspace": "~/.openclaw/workspace-researcher",
|
|
811
|
+
"agentDir": "~/.openclaw/config/agents/researcher/agent"
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
},
|
|
815
|
+
"gateway": { "port": 18789, "auth": { "mode": "token", "token": "\${OPENCLAW_AUTH_TOKEN}" } },
|
|
816
|
+
"models": {
|
|
817
|
+
"providers": {
|
|
818
|
+
"openrouter": {
|
|
819
|
+
"apiKey": "\${OPENROUTER_API_KEY}",
|
|
820
|
+
"baseUrl": "https://openrouter.ai/api/v1",
|
|
821
|
+
"models": [
|
|
822
|
+
{ "id": "openrouter/google/gemini-2.5-flash", "name": "Gemini 2.5 Flash" }
|
|
823
|
+
]
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
"tools": { "allow": [], "deny": ["dangerous-tool"], "agentToAgent": { "enabled": true } },
|
|
828
|
+
"env": { "OPENROUTER_API_KEY": "\${OPENROUTER_API_KEY}", "MY_VAR": "hello" }
|
|
829
|
+
}`;
|
|
830
|
+
const SOUL_MD = `> A test topology for unit testing.
|
|
831
|
+
|
|
832
|
+
## Identity
|
|
833
|
+
- Name: Test Topology
|
|
834
|
+
- Version: 2.0.0
|
|
835
|
+
- Patterns: supervisor, fan-out
|
|
836
|
+
|
|
837
|
+
## Mission
|
|
838
|
+
A test topology for unit testing.
|
|
839
|
+
|
|
840
|
+
## Roles
|
|
841
|
+
### Strategy
|
|
842
|
+
Plan and coordinate
|
|
843
|
+
|
|
844
|
+
### Research
|
|
845
|
+
Find information
|
|
846
|
+
|
|
847
|
+
## Ethical Guardrails
|
|
848
|
+
- Tool restrictions: none
|
|
849
|
+
|
|
850
|
+
## Parameters
|
|
851
|
+
|
|
852
|
+
- **campaign**: string (required)
|
|
853
|
+
- **mode**: string = intelligence
|
|
854
|
+
|
|
855
|
+
## Interface
|
|
856
|
+
|
|
857
|
+
- Entry: commander
|
|
858
|
+
- Exit: commander
|
|
859
|
+
|
|
860
|
+
## Triggers
|
|
861
|
+
|
|
862
|
+
### launch
|
|
863
|
+
Pattern: \`/launch <CAMPAIGN>\`
|
|
864
|
+
Argument: CAMPAIGN
|
|
865
|
+
|
|
866
|
+
### status
|
|
867
|
+
Pattern: \`/status <CAMPAIGN>\`
|
|
868
|
+
Argument: CAMPAIGN
|
|
869
|
+
|
|
870
|
+
## Environment
|
|
871
|
+
|
|
872
|
+
- \`OPENROUTER_API_KEY\`: \${OPENROUTER_API_KEY}
|
|
873
|
+
- \`MY_VAR\`: hello
|
|
874
|
+
`;
|
|
875
|
+
const AGENTS_MD = `# Sub-Agent Definitions
|
|
876
|
+
|
|
877
|
+
## Pipeline Overview
|
|
878
|
+
Test Topology uses a supervisor + fan-out topology.
|
|
879
|
+
|
|
880
|
+
## Agents
|
|
881
|
+
|
|
882
|
+
### Commander
|
|
883
|
+
- **Model:** openrouter/google/gemini-2.5-flash
|
|
884
|
+
- **Phase:** 1
|
|
885
|
+
- **Role:** Plan and coordinate
|
|
886
|
+
- **Tools allowed:** Read, Write, Bash
|
|
887
|
+
- **Tools denied:** none
|
|
888
|
+
- **Reads:** workspace
|
|
889
|
+
- **Writes:** workspace
|
|
890
|
+
- **Skills:** campaign-management
|
|
891
|
+
- **Max turns:** 100
|
|
892
|
+
|
|
893
|
+
#### Instructions
|
|
894
|
+
|
|
895
|
+
You are the commander.
|
|
896
|
+
|
|
897
|
+
Do important things.
|
|
898
|
+
- **Maximum execution time:** 30m
|
|
899
|
+
- **Retry:** max 2 attempts
|
|
900
|
+
|
|
901
|
+
### Researcher
|
|
902
|
+
- **Model:** openrouter/google/gemini-2.5-flash
|
|
903
|
+
- **Phase:** 2
|
|
904
|
+
- **Role:** Find information
|
|
905
|
+
- **Tools allowed:** Read, Write
|
|
906
|
+
- **Tools denied:** none
|
|
907
|
+
- **Reads:** workspace, domains
|
|
908
|
+
- **Writes:** workspace
|
|
909
|
+
- **Rate limit:** 30/hour
|
|
910
|
+
- **Circuit breaker:** threshold=5, window=30m, cooldown=1h
|
|
911
|
+
- **On failure:** continue
|
|
912
|
+
|
|
913
|
+
#### Instructions
|
|
914
|
+
|
|
915
|
+
You are the researcher.
|
|
916
|
+
- **Maximum execution time:** 45m
|
|
917
|
+
- **Retry:** max 3 attempts
|
|
918
|
+
|
|
919
|
+
## Group Chat Coordination
|
|
920
|
+
|
|
921
|
+
### Strategy Session
|
|
922
|
+
- **Members:** commander, researcher
|
|
923
|
+
- **Speaker selection:** round-robin
|
|
924
|
+
- **Max rounds:** 5
|
|
925
|
+
- **Termination:** Commander confirms the plan
|
|
926
|
+
- **Description:** Strategy planning session
|
|
927
|
+
- **Timeout:** 30m
|
|
928
|
+
|
|
929
|
+
## Flow
|
|
930
|
+
|
|
931
|
+
### Execution Order
|
|
932
|
+
1. commander -> strategy-session
|
|
933
|
+
2. strategy-session -> researcher
|
|
934
|
+
3. researcher -x-> commander (max 3 iterations)
|
|
935
|
+
|
|
936
|
+
### Bounded Loops
|
|
937
|
+
- researcher <-> commander: max 3 iterations (prevents infinite loops)
|
|
938
|
+
|
|
939
|
+
### Error Handling
|
|
940
|
+
- On error in researcher: route to commander
|
|
941
|
+
|
|
942
|
+
## Gates
|
|
943
|
+
|
|
944
|
+
### Quality Check
|
|
945
|
+
- **After:** researcher
|
|
946
|
+
- **Before:** commander
|
|
947
|
+
- **On failure:** bounce-back
|
|
948
|
+
- **Behavior:** blocking
|
|
949
|
+
- **Checks:** output-valid, data-complete
|
|
950
|
+
|
|
951
|
+
## Schedule
|
|
952
|
+
|
|
953
|
+
### Morning Run
|
|
954
|
+
- **Agent:** commander
|
|
955
|
+
- **Cron:** \`0 9 * * 0-4\`
|
|
956
|
+
- **Enabled:** true
|
|
957
|
+
|
|
958
|
+
## Hooks
|
|
959
|
+
|
|
960
|
+
### Block Destructive
|
|
961
|
+
- **Event:** PreToolUse
|
|
962
|
+
- **Run:** exit 2
|
|
963
|
+
- **Type:** command
|
|
964
|
+
|
|
965
|
+
### Log Completion
|
|
966
|
+
- **Event:** AgentStop
|
|
967
|
+
- **Run:** scripts/log.sh
|
|
968
|
+
- **Type:** command
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## Test Topology -- Topology Overview
|
|
973
|
+
|
|
974
|
+
### Architecture
|
|
975
|
+
- Patterns: supervisor, fan-out
|
|
976
|
+
- Agents: 2
|
|
977
|
+
- Gates: 1
|
|
978
|
+
- Version: 2.0.0
|
|
979
|
+
`;
|
|
980
|
+
const SKILL_MD = `---
|
|
981
|
+
name: campaign-management
|
|
982
|
+
description: Manage campaigns end to end
|
|
983
|
+
user-invocable: true
|
|
984
|
+
---
|
|
985
|
+
|
|
986
|
+
This skill manages campaign lifecycle.
|
|
987
|
+
`;
|
|
988
|
+
const MEMORY_MD = `# Memory Structure
|
|
989
|
+
|
|
990
|
+
## Workspace
|
|
991
|
+
- Path: \`campaigns/{CAMPAIGN}/\`
|
|
992
|
+
- Structure: messages, outbox, logs
|
|
993
|
+
|
|
994
|
+
## Domains
|
|
995
|
+
- Path: \`campaigns/{CAMPAIGN}/intel/\`
|
|
996
|
+
`;
|
|
997
|
+
// ---------------------------------------------------------------------------
|
|
998
|
+
// parseOpenClawJson
|
|
999
|
+
// ---------------------------------------------------------------------------
|
|
1000
|
+
describe("parseOpenClawJson", () => {
|
|
1001
|
+
it("extracts agent list with models", () => {
|
|
1002
|
+
const result = parseOpenClawJson(OPENCLAW_JSON);
|
|
1003
|
+
expect(result.agents).toHaveLength(2);
|
|
1004
|
+
expect(result.agents[0].id).toBe("commander");
|
|
1005
|
+
expect(result.agents[0].model).toBe("openrouter/google/gemini-2.5-flash");
|
|
1006
|
+
expect(result.agents[0].subagents).toEqual(["researcher", "worker"]);
|
|
1007
|
+
});
|
|
1008
|
+
it("extracts fallback chains", () => {
|
|
1009
|
+
const result = parseOpenClawJson(OPENCLAW_JSON);
|
|
1010
|
+
expect(result.agents[1].fallbacks).toEqual(["openrouter/google/gemini-2.5-flash-lite"]);
|
|
1011
|
+
});
|
|
1012
|
+
it("extracts providers", () => {
|
|
1013
|
+
const result = parseOpenClawJson(OPENCLAW_JSON);
|
|
1014
|
+
expect(result.providers).toHaveLength(1);
|
|
1015
|
+
expect(result.providers[0].name).toBe("openrouter");
|
|
1016
|
+
expect(result.providers[0].apiKey).toBe("${OPENROUTER_API_KEY}");
|
|
1017
|
+
expect(result.providers[0].baseUrl).toBe("https://openrouter.ai/api/v1");
|
|
1018
|
+
expect(result.providerNames).toEqual(["openrouter"]);
|
|
1019
|
+
});
|
|
1020
|
+
it("extracts tools settings and env", () => {
|
|
1021
|
+
const result = parseOpenClawJson(OPENCLAW_JSON);
|
|
1022
|
+
expect(result.settings.deny).toEqual(["dangerous-tool"]);
|
|
1023
|
+
expect(result.env.MY_VAR).toBe("hello");
|
|
1024
|
+
});
|
|
1025
|
+
it("extracts gateway extensions", () => {
|
|
1026
|
+
const result = parseOpenClawJson(OPENCLAW_JSON);
|
|
1027
|
+
expect(result.extensions["gateway-port"]).toBe(18789);
|
|
1028
|
+
expect(result.extensions["auth-mode"]).toBe("token");
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
// ---------------------------------------------------------------------------
|
|
1032
|
+
// parseSoulMd
|
|
1033
|
+
// ---------------------------------------------------------------------------
|
|
1034
|
+
describe("parseSoulMd", () => {
|
|
1035
|
+
it("extracts identity", () => {
|
|
1036
|
+
const result = parseSoulMd(SOUL_MD);
|
|
1037
|
+
expect(result.name).toBe("test-topology");
|
|
1038
|
+
expect(result.version).toBe("2.0.0");
|
|
1039
|
+
expect(result.patterns).toEqual(["supervisor", "fan-out"]);
|
|
1040
|
+
});
|
|
1041
|
+
it("extracts description from quote block", () => {
|
|
1042
|
+
const result = parseSoulMd(SOUL_MD);
|
|
1043
|
+
expect(result.description).toBe("A test topology for unit testing.");
|
|
1044
|
+
});
|
|
1045
|
+
it("extracts roles", () => {
|
|
1046
|
+
const result = parseSoulMd(SOUL_MD);
|
|
1047
|
+
expect(result.roles.strategy).toBe("Plan and coordinate");
|
|
1048
|
+
expect(result.roles.research).toBe("Find information");
|
|
1049
|
+
});
|
|
1050
|
+
it("extracts parameters", () => {
|
|
1051
|
+
const result = parseSoulMd(SOUL_MD);
|
|
1052
|
+
expect(result.params).toHaveLength(2);
|
|
1053
|
+
expect(result.params[0]).toEqual({ name: "campaign", type: "string", default: undefined, required: true });
|
|
1054
|
+
expect(result.params[1]).toEqual({ name: "mode", type: "string", default: "intelligence", required: false });
|
|
1055
|
+
});
|
|
1056
|
+
it("extracts interface endpoints", () => {
|
|
1057
|
+
const result = parseSoulMd(SOUL_MD);
|
|
1058
|
+
expect(result.interfaceEndpoints).toEqual({ entry: "commander", exit: "commander" });
|
|
1059
|
+
});
|
|
1060
|
+
it("extracts triggers", () => {
|
|
1061
|
+
const result = parseSoulMd(SOUL_MD);
|
|
1062
|
+
expect(result.triggers).toHaveLength(2);
|
|
1063
|
+
expect(result.triggers[0]).toEqual({ name: "launch", pattern: "/launch <CAMPAIGN>", argument: "CAMPAIGN" });
|
|
1064
|
+
expect(result.triggers[1]).toEqual({ name: "status", pattern: "/status <CAMPAIGN>", argument: "CAMPAIGN" });
|
|
1065
|
+
});
|
|
1066
|
+
it("extracts environment variables", () => {
|
|
1067
|
+
const result = parseSoulMd(SOUL_MD);
|
|
1068
|
+
expect(result.env["OPENROUTER_API_KEY"]).toBe("${OPENROUTER_API_KEY}");
|
|
1069
|
+
expect(result.env["MY_VAR"]).toBe("hello");
|
|
1070
|
+
});
|
|
1071
|
+
});
|
|
1072
|
+
// ---------------------------------------------------------------------------
|
|
1073
|
+
// parseAgentsMd
|
|
1074
|
+
// ---------------------------------------------------------------------------
|
|
1075
|
+
describe("parseAgentsMd", () => {
|
|
1076
|
+
it("extracts agents with basic properties", () => {
|
|
1077
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1078
|
+
expect(result.agents).toHaveLength(2);
|
|
1079
|
+
const cmd = result.agents[0];
|
|
1080
|
+
expect(cmd.id).toBe("commander");
|
|
1081
|
+
expect(cmd.phase).toBe(1);
|
|
1082
|
+
expect(cmd.tools).toEqual(["Read", "Write", "Bash"]);
|
|
1083
|
+
expect(cmd.reads).toEqual(["workspace"]);
|
|
1084
|
+
expect(cmd.writes).toEqual(["workspace"]);
|
|
1085
|
+
expect(cmd.skills).toEqual(["campaign-management"]);
|
|
1086
|
+
expect(cmd.maxTurns).toBe(100);
|
|
1087
|
+
});
|
|
1088
|
+
it("extracts agent prompt (instructions)", () => {
|
|
1089
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1090
|
+
const cmd = result.agents[0];
|
|
1091
|
+
expect(cmd.prompt).toContain("You are the commander.");
|
|
1092
|
+
expect(cmd.prompt).toContain("Do important things.");
|
|
1093
|
+
});
|
|
1094
|
+
it("extracts trailing properties after instructions", () => {
|
|
1095
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1096
|
+
const cmd = result.agents[0];
|
|
1097
|
+
expect(cmd.timeout).toBe("30m");
|
|
1098
|
+
expect(cmd.retry).toBe(2);
|
|
1099
|
+
});
|
|
1100
|
+
it("extracts rate limit and circuit breaker", () => {
|
|
1101
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1102
|
+
const researcher = result.agents[1];
|
|
1103
|
+
expect(researcher.rateLimit).toBe("30/hour");
|
|
1104
|
+
expect(researcher.circuitBreaker).toEqual({
|
|
1105
|
+
threshold: 5,
|
|
1106
|
+
window: "30m",
|
|
1107
|
+
cooldown: "1h",
|
|
1108
|
+
});
|
|
1109
|
+
expect(researcher.onFail).toBe("continue");
|
|
1110
|
+
});
|
|
1111
|
+
it("extracts group chat", () => {
|
|
1112
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1113
|
+
expect(result.groups).toHaveLength(1);
|
|
1114
|
+
const group = result.groups[0];
|
|
1115
|
+
expect(group.id).toBe("strategy-session");
|
|
1116
|
+
expect(group.members).toEqual(["commander", "researcher"]);
|
|
1117
|
+
expect(group.speakerSelection).toBe("round-robin");
|
|
1118
|
+
expect(group.maxRounds).toBe(5);
|
|
1119
|
+
expect(group.termination).toBe("Commander confirms the plan");
|
|
1120
|
+
expect(group.timeout).toBe("30m");
|
|
1121
|
+
});
|
|
1122
|
+
it("extracts flow edges including error edges", () => {
|
|
1123
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1124
|
+
expect(result.edges).toHaveLength(3);
|
|
1125
|
+
expect(result.edges[0]).toMatchObject({ from: "commander", to: "strategy-session" });
|
|
1126
|
+
expect(result.edges[0].isError).toBeUndefined();
|
|
1127
|
+
expect(result.edges[1]).toMatchObject({ from: "strategy-session", to: "researcher" });
|
|
1128
|
+
expect(result.edges[1].isError).toBeUndefined();
|
|
1129
|
+
expect(result.edges[2]).toMatchObject({ from: "researcher", to: "commander", isError: true, maxIterations: 3 });
|
|
1130
|
+
});
|
|
1131
|
+
it("extracts gates", () => {
|
|
1132
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1133
|
+
expect(result.gates).toHaveLength(1);
|
|
1134
|
+
const gate = result.gates[0];
|
|
1135
|
+
expect(gate.id).toBe("quality-check");
|
|
1136
|
+
expect(gate.after).toBe("researcher");
|
|
1137
|
+
expect(gate.before).toBe("commander");
|
|
1138
|
+
expect(gate.onFail).toBe("bounce-back");
|
|
1139
|
+
expect(gate.behavior).toBe("blocking");
|
|
1140
|
+
expect(gate.checks).toEqual(["output-valid", "data-complete"]);
|
|
1141
|
+
});
|
|
1142
|
+
it("extracts schedule", () => {
|
|
1143
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1144
|
+
expect(result.schedules).toHaveLength(1);
|
|
1145
|
+
expect(result.schedules[0]).toEqual({
|
|
1146
|
+
id: "morning-run",
|
|
1147
|
+
agent: "commander",
|
|
1148
|
+
cron: "0 9 * * 0-4",
|
|
1149
|
+
enabled: true,
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
it("extracts hooks", () => {
|
|
1153
|
+
const result = parseAgentsMd(AGENTS_MD);
|
|
1154
|
+
expect(result.hooks).toHaveLength(2);
|
|
1155
|
+
expect(result.hooks[0].name).toBe("block-destructive");
|
|
1156
|
+
expect(result.hooks[0].on).toBe("PreToolUse");
|
|
1157
|
+
expect(result.hooks[0].run).toBe("exit 2");
|
|
1158
|
+
expect(result.hooks[1].name).toBe("log-completion");
|
|
1159
|
+
expect(result.hooks[1].on).toBe("AgentStop");
|
|
1160
|
+
});
|
|
1161
|
+
});
|
|
1162
|
+
// ---------------------------------------------------------------------------
|
|
1163
|
+
// parseSkillFile
|
|
1164
|
+
// ---------------------------------------------------------------------------
|
|
1165
|
+
describe("parseSkillFile (openclaw)", () => {
|
|
1166
|
+
it("extracts skill from frontmatter", () => {
|
|
1167
|
+
const skill = parseSkillFile("campaign-management", SKILL_MD);
|
|
1168
|
+
expect(skill.id).toBe("campaign-management");
|
|
1169
|
+
expect(skill.description).toBe("Manage campaigns end to end");
|
|
1170
|
+
expect(skill.userInvocable).toBe(true);
|
|
1171
|
+
});
|
|
1172
|
+
it("handles skill without frontmatter", () => {
|
|
1173
|
+
const skill = parseSkillFile("my-skill", "This skill does things.");
|
|
1174
|
+
expect(skill.id).toBe("my-skill");
|
|
1175
|
+
expect(skill.description).toBe("This skill does things.");
|
|
1176
|
+
expect(skill.userInvocable).toBeUndefined();
|
|
1177
|
+
});
|
|
1178
|
+
});
|
|
1179
|
+
// ---------------------------------------------------------------------------
|
|
1180
|
+
// parseMemoryMd
|
|
1181
|
+
// ---------------------------------------------------------------------------
|
|
1182
|
+
describe("parseMemoryMd", () => {
|
|
1183
|
+
it("extracts workspace and domains", () => {
|
|
1184
|
+
const result = parseMemoryMd(MEMORY_MD);
|
|
1185
|
+
const ws = result.memory.workspace;
|
|
1186
|
+
expect(ws.path).toBe("campaigns/{CAMPAIGN}/");
|
|
1187
|
+
expect(ws.structure).toEqual(["messages", "outbox", "logs"]);
|
|
1188
|
+
const domains = result.memory.domains;
|
|
1189
|
+
expect(domains.path).toBe("campaigns/{CAMPAIGN}/intel/");
|
|
1190
|
+
});
|
|
1191
|
+
});
|
|
1192
|
+
// ---------------------------------------------------------------------------
|
|
1193
|
+
// parseCronJobsJson
|
|
1194
|
+
// ---------------------------------------------------------------------------
|
|
1195
|
+
describe("parseCronJobsJson", () => {
|
|
1196
|
+
it("parses cron jobs array", () => {
|
|
1197
|
+
const json = JSON.stringify({
|
|
1198
|
+
jobs: [
|
|
1199
|
+
{ id: "morning", agentId: "commander", schedule: { expr: "0 9 * * *" }, enabled: true },
|
|
1200
|
+
{ id: "evening", agentId: "reporter", cron: "0 18 * * *", enabled: false },
|
|
1201
|
+
],
|
|
1202
|
+
});
|
|
1203
|
+
const jobs = parseCronJobsJson(json);
|
|
1204
|
+
expect(jobs).toHaveLength(2);
|
|
1205
|
+
expect(jobs[0]).toEqual({ id: "morning", agent: "commander", cron: "0 9 * * *", enabled: true });
|
|
1206
|
+
expect(jobs[1]).toEqual({ id: "evening", agent: "reporter", cron: "0 18 * * *", enabled: false });
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
// ---------------------------------------------------------------------------
|
|
1210
|
+
// importOpenClaw (integration)
|
|
1211
|
+
// ---------------------------------------------------------------------------
|
|
1212
|
+
describe("importOpenClaw", () => {
|
|
1213
|
+
const minimalFiles = [
|
|
1214
|
+
{ path: "openclaw.json", content: OPENCLAW_JSON },
|
|
1215
|
+
{ path: "SOUL.md", content: SOUL_MD },
|
|
1216
|
+
{ path: "AGENTS.md", content: AGENTS_MD },
|
|
1217
|
+
{ path: "MEMORY.md", content: MEMORY_MD },
|
|
1218
|
+
{ path: "skills/campaign-management/SKILL.md", content: SKILL_MD },
|
|
1219
|
+
];
|
|
1220
|
+
it("produces a complete TopologyAST", () => {
|
|
1221
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1222
|
+
expect(ast.topology.name).toBe("test-topology");
|
|
1223
|
+
expect(ast.topology.version).toBe("2.0.0");
|
|
1224
|
+
expect(ast.topology.patterns).toEqual(["supervisor", "fan-out"]);
|
|
1225
|
+
expect(ast.topology.description).toBe("A test topology for unit testing.");
|
|
1226
|
+
});
|
|
1227
|
+
it("imports agents with reverse-mapped models", () => {
|
|
1228
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1229
|
+
const agents = ast.nodes.filter((n) => n.type === "agent");
|
|
1230
|
+
expect(agents).toHaveLength(2);
|
|
1231
|
+
const cmd = agents.find((a) => a.id === "commander");
|
|
1232
|
+
expect(cmd.model).toBe("google/gemini-2.5-flash");
|
|
1233
|
+
expect(cmd.phase).toBe(1);
|
|
1234
|
+
expect(cmd.timeout).toBe("30m");
|
|
1235
|
+
const researcher = agents.find((a) => a.id === "researcher");
|
|
1236
|
+
expect(researcher.model).toBe("google/gemini-2.5-flash");
|
|
1237
|
+
expect(researcher.fallbackChain).toEqual(["google/gemini-2.5-flash-lite"]);
|
|
1238
|
+
});
|
|
1239
|
+
it("imports roles, matching agents to role keys", () => {
|
|
1240
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1241
|
+
expect(ast.roles.strategy).toBe("Plan and coordinate");
|
|
1242
|
+
expect(ast.roles.research).toBe("Find information");
|
|
1243
|
+
const cmd = ast.nodes.find((n) => n.id === "commander");
|
|
1244
|
+
expect(cmd.role).toBe("strategy");
|
|
1245
|
+
});
|
|
1246
|
+
it("imports groups, gates, edges, hooks, schedules", () => {
|
|
1247
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1248
|
+
const groups = ast.nodes.filter((n) => n.type === "group");
|
|
1249
|
+
expect(groups).toHaveLength(1);
|
|
1250
|
+
const gates = ast.nodes.filter((n) => n.type === "gate");
|
|
1251
|
+
expect(gates).toHaveLength(1);
|
|
1252
|
+
expect(ast.edges).toHaveLength(3);
|
|
1253
|
+
expect(ast.hooks).toHaveLength(2);
|
|
1254
|
+
expect(ast.schedules).toHaveLength(1);
|
|
1255
|
+
});
|
|
1256
|
+
it("imports skills", () => {
|
|
1257
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1258
|
+
expect(ast.skills).toHaveLength(1);
|
|
1259
|
+
expect(ast.skills[0].id).toBe("campaign-management");
|
|
1260
|
+
expect(ast.skills[0].userInvocable).toBe(true);
|
|
1261
|
+
});
|
|
1262
|
+
it("imports params, triggers, interface endpoints", () => {
|
|
1263
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1264
|
+
expect(ast.params).toHaveLength(2);
|
|
1265
|
+
expect(ast.triggers).toHaveLength(2);
|
|
1266
|
+
expect(ast.interfaceEndpoints).toEqual({ entry: "commander", exit: "commander" });
|
|
1267
|
+
});
|
|
1268
|
+
it("imports providers with reverse-mapped models", () => {
|
|
1269
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1270
|
+
expect(ast.providers).toHaveLength(1);
|
|
1271
|
+
expect(ast.providers[0].name).toBe("openrouter");
|
|
1272
|
+
expect(ast.providers[0].default).toBe(true);
|
|
1273
|
+
});
|
|
1274
|
+
it("imports env and settings", () => {
|
|
1275
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1276
|
+
expect(ast.env["MY_VAR"]).toBe("hello");
|
|
1277
|
+
expect(ast.settings.deny).toEqual(["dangerous-tool"]);
|
|
1278
|
+
});
|
|
1279
|
+
it("imports memory", () => {
|
|
1280
|
+
const ast = importOpenClaw(minimalFiles, "test-topology");
|
|
1281
|
+
expect(ast.memory.workspace?.path).toBe("campaigns/{CAMPAIGN}/");
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
// ---------------------------------------------------------------------------
|
|
1285
|
+
// importFromPlatform — OpenClaw dispatch
|
|
1286
|
+
// ---------------------------------------------------------------------------
|
|
1287
|
+
describe("importFromPlatform (openclaw)", () => {
|
|
1288
|
+
it("dispatches to openclaw", () => {
|
|
1289
|
+
const files = [
|
|
1290
|
+
{ path: "openclaw.json", content: OPENCLAW_JSON },
|
|
1291
|
+
{ path: "SOUL.md", content: SOUL_MD },
|
|
1292
|
+
{ path: "AGENTS.md", content: AGENTS_MD },
|
|
1293
|
+
];
|
|
1294
|
+
const atSource = importFromPlatform(files, "openclaw", "test");
|
|
1295
|
+
// SOUL.md Identity overrides the topologyName param
|
|
1296
|
+
expect(atSource).toContain("topology test-topology");
|
|
1297
|
+
expect(atSource).toContain("agent commander");
|
|
1298
|
+
expect(atSource).toContain("agent researcher");
|
|
1299
|
+
});
|
|
1300
|
+
it("falls back to topologyName when SOUL.md is missing", () => {
|
|
1301
|
+
const files = [
|
|
1302
|
+
{ path: "openclaw.json", content: OPENCLAW_JSON },
|
|
1303
|
+
{ path: "AGENTS.md", content: AGENTS_MD },
|
|
1304
|
+
];
|
|
1305
|
+
const atSource = importFromPlatform(files, "openclaw", "my-project");
|
|
1306
|
+
expect(atSource).toContain("topology my-project");
|
|
1307
|
+
});
|
|
1308
|
+
it("generates parseable .at output", () => {
|
|
1309
|
+
const files = [
|
|
1310
|
+
{ path: "openclaw.json", content: OPENCLAW_JSON },
|
|
1311
|
+
{ path: "SOUL.md", content: SOUL_MD },
|
|
1312
|
+
{ path: "AGENTS.md", content: AGENTS_MD },
|
|
1313
|
+
{ path: "MEMORY.md", content: MEMORY_MD },
|
|
1314
|
+
{ path: "skills/campaign-management/SKILL.md", content: SKILL_MD },
|
|
1315
|
+
];
|
|
1316
|
+
const atSource = importFromPlatform(files, "openclaw", "test");
|
|
1317
|
+
const ast = parse(atSource);
|
|
1318
|
+
expect(ast.topology.name).toBe("test-topology");
|
|
1319
|
+
expect(ast.nodes.filter((n) => n.type === "agent").length).toBe(2);
|
|
1320
|
+
});
|
|
1321
|
+
it("validates with zero errors (warnings allowed)", () => {
|
|
1322
|
+
const files = [
|
|
1323
|
+
{ path: "openclaw.json", content: OPENCLAW_JSON },
|
|
1324
|
+
{ path: "SOUL.md", content: SOUL_MD },
|
|
1325
|
+
{ path: "AGENTS.md", content: AGENTS_MD },
|
|
1326
|
+
{ path: "MEMORY.md", content: MEMORY_MD },
|
|
1327
|
+
{ path: "skills/campaign-management/SKILL.md", content: SKILL_MD },
|
|
1328
|
+
];
|
|
1329
|
+
const atSource = importFromPlatform(files, "openclaw", "test");
|
|
1330
|
+
const ast = parse(atSource);
|
|
1331
|
+
const errors = validate(ast).filter((e) => e.level === "error");
|
|
1332
|
+
expect(errors).toEqual([]);
|
|
1333
|
+
});
|
|
1334
|
+
});
|
|
781
1335
|
//# sourceMappingURL=import.test.js.map
|